PostgreSQL: Serverseitige Programmierung in der menschlichen Sprache (PL / Perl, PL / Python, PL / v8)

Postgres ist berühmt für seine Erweiterbarkeit, die auch für die Unterstützung von prozeduralen Sprachen (PL) gilt. Niemand kann sich einer Sprache mit einer Liste von Sprachen dieser Länge rühmen , und möglicherweise ist diese Liste überhaupt nicht beschränkt: Um die Sprache mit dem Server zu verbinden, ist kein zusätzlicher Aufwand erforderlich. Sie können sogar Ihre eigene Sprache erstellen und sie zu einer prozeduralen Serversprache machen. Änderungen im DBMS erfordern dies nicht. Wie so viel mehr wurde diese Erweiterbarkeit von Anfang an in die Postgres-Architektur integriert.

Es ist möglich und manchmal notwendig, PL-Sprachen für Aufgaben zu schreiben. Besser noch, wenn jemand ein solches Framework zum Schreiben von Sprachen schreibt, damit Sie nicht in C schreiben können, sondern eine Sprache wählen, die für einen Sprachentwickler komfortabler ist. Wie bei FDW, das in Python geschrieben werden kann .

Dieser Artikel wurde auf der Grundlage einer Reihe von Berichten und Meisterkursen zu diesem Thema verfasst, die der Autor auf den Konferenzen PgConf.Russia 2019 , PgConf.Russia 2018 und DevConf 2017 verfasst hat .

Hier geht es nicht um Exotik, sondern um die gängigsten prozeduralen Sprachen PL / Perl, PL / Python und PL / V8 (d. H. JavaScript) und den Vergleich ihrer Fähigkeiten mit PL / pgSQL.

Wann lohnt es sich, diese Sprachen zu verwenden? Wann fehlen SQL und PL / pgSQL?

  • Dann, wenn Sie mit komplexen Strukturen arbeiten müssen, mit Algorithmen: zum Beispiel Bäume durchlaufen oder wenn HTML- oder XML-Analyse erforderlich ist, insbesondere wenn Sie sie aus Archiven extrahieren;
  • Wenn Sie komplexes SQL (Berichte, ORM) dynamisch generieren müssen. Unter PL / pgSQL ist dies nicht nur unpraktisch, sondern funktioniert in einigen Fällen auch langsamer.
  • Perl Python, C/C++, Perl Python . . , Oracle. , Postgres . Perl Python .
  • — . , , untrusted- ( — . ), Perlu Python(3)u, PL/V8. Postgres , , FDW, , . . !
  • Und noch etwas: Wenn Sie etwas in C schreiben möchten, können Sie einen Prototyp in diesen Sprachen erstellen, der für eine schnelle Entwicklung besser geeignet ist.

So binden Sie eine Sprache in Postgres ein


Um die Sprache zu implementieren, die Sie benötigen: Schreiben Sie in C eine bis drei Funktionen:

  • HANDLER - ein Call-Handler, der eine Funktion in der Sprache ausführt (dies ist ein erforderlicher Teil);
  • INLINE - anonymer Blockhandler (wenn die Sprache anonyme Blöcke unterstützen soll);
  • VALIDATOR - Codeüberprüfungsfunktion beim Erstellen einer Funktion (wenn diese Überprüfung durchgeführt werden soll).

Dies wird in der Dokumentation hier und hier ausführlich beschrieben .

"Sprachen aus der Box" und andere Sprachen


Es gibt nur vier Sprachen, die "out of the box" unterstützt werden: PL / pgSQL , PL / Perl , PL / Python und PL / Tcl , aber das Kitzeln ist eher eine Hommage an die Geschichte: Nur wenige Leute verwenden es jetzt, wir werden nicht mehr darüber sprechen.
PL / Perl, PL / Python und natürlich PL / pgSQL werden von der Postgres-Community unterstützt. Die Unterstützung für andere, sofort einsatzbereite Sprachen liegt bei ihren Betreuern - Unternehmen, Communitys oder bestimmten Entwicklern, die daran interessiert sind, dass die Sprache im DBMS funktioniert. PL / V8 fördert Google. Aber von Zeit zu Zeit gibt es Gründebezweifle die wolkenlose Zukunft von PL / V8. Der aktuelle PL / V8-Projektbetreuer von Google, Jerry Sievert, erwägt die serverbasierte JS-Unterstützung auf Postgres-Basis, die auf einer anderen Engine (wie QuickJS) basiert, da PL / V8 schwierig zu erstellen ist und 3 bis 5 GB erfordert Alle möglichen Dinge unter Linux beim Erstellen, und dies führt häufig zu Problemen auf verschiedenen Betriebssystemen. Der PL / V8 ist jedoch weit verbreitet und gründlich getestet. Es ist möglich, dass PL / JS als Alternative zu einer anderen JS-Engine oder vorerst nur als Name erscheint, an den wir uns während der Übergangszeit gewöhnen werden.

PL / Java wird selten verwendet. Ich persönlich musste nicht in PL / Java schreiben, da in PL / Perl und in PL / V8 genügend Funktionen für fast alle Aufgaben vorhanden sind. Selbst Python fügt keine besonderen Funktionen hinzu. PL / R.Nützlich für diejenigen, die Statistiken mögen und diese Sprache lieben. Wir werden auch hier nicht über ihn sprechen.

Beliebte Sprachen sind beim Schreiben von Speichern nicht unbedingt beliebt: Es gibt PL / PHP, aber jetzt wird es praktisch von niemandem unterstützt - es gibt nur wenige, die Serverprozeduren darauf schreiben möchten. Für die PL / Ruby-Sprache ist das Bild irgendwie das gleiche, obwohl die Sprache moderner zu sein scheint.

Eine Go-basierte prozedurale Sprache wird entwickelt, siehe PL / Go und anscheinend auch PL / Lua . Es wird notwendig sein, sie zu studieren. Für hartnäckige Fans der Shell gibt es sogar PL / Sh , es ist schwer vorstellbar, wofür es sein könnte.

Es gibt mindestens eine domänenspezifische prozedurale Sprache (DSL), die eng auf ihre Aufgabe spezialisiert ist - PL / Proxy, das früher sehr beliebt war, um die Serverlast zu vertreten und auszugleichen.

In diesem Artikel werden die wichtigsten, am häufigsten verwendeten Sprachen behandelt. Dies ist natürlich PL / PgSQL, PL / Perl, PL / Python und PL / V8, wir werden sie unten PL / * nennen .

Sprachen „out of the box“ werden fast buchstäblich sofort installiert - normalerweise ist die Installation schmerzlos. Um PL / V8 zu installieren, ist dies fast eine Leistung, wenn Sie im Repository Ihres Betriebssystems kein Paket mit der erforderlichen Version gefunden haben, da Sie dafür tatsächlich die gesamte V8 oder mit anderen Worten Chromium erstellen müssen. Gleichzeitig wird die gesamte Entwicklungsinfrastruktur zusammen mit V8 selbst von google.com heruntergeladen - rechnen Sie mit ein paar Gigabyte Datenverkehr. Für Postgres 11 unter Ubuntu ist das PL / V8-Paket noch nicht erschienen. Bisher ist nur V8 für PG 10 im Repository verfügbar. Wenn Sie möchten, stellen Sie es von Hand zusammen. Es ist auch wichtig, dass die Version, die Sie im Repository finden, höchstwahrscheinlich ziemlich alt ist. Zum Zeitpunkt der Veröffentlichung des Artikels ist die neueste Version 2.3.14.

Nachdem die Sprache selbst installiert wurde, müssen Sie auch die Sprache „erstellen“ - im Systemverzeichnis registrieren. Dies sollte vom Team durchgeführt werden.

CREATE EXTENSION plperl;

(Anstelle von plperl können Sie den Namen einer anderen Sprache ersetzen, es gibt bestimmte Nuancen, siehe unten).
Wir schauen uns an, was passiert ist:

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 muss nicht speziell erstellt werden, es befindet sich immer bereits in der Datenbank.

Beachtung! PL / pgSQL sollte nicht mit SQL verwechselt werden. Dies ist eine andere Sprache. Postgres kann jedoch auch Funktionen in einfachem SQL schreiben.

Standards


In der Welt von DBMS wird häufig über die Einhaltung von SQL-Standards gesprochen. Verfahrenssprachen haben auch Standards, über die jedoch nicht so oft gesprochen wird. Der SQL / PSM- Standard ist in hohem Maße mit der prozeduralen Sprache DB2 kompatibel. Die Implementierung ist weit entfernt von PL / pgSQL, obwohl sie konzeptionell nahe beieinander liegen.

SQL / JRT ist der Standard für Java-Prozeduren, und PL / Java passt gut dazu.

Vertrauenswürdige und nicht vertrauenswürdige Sprachen


Postgres-Verfahrenssprachen sind vertrauenswürdig (VERTRAUEN) und nicht vertrauenswürdig (NICHT VERTRAUEN).
In VERTRAUENEN Sprachen gibt es keine Möglichkeit der direkten Arbeit mit E / A, einschließlich des Netzwerks, und tatsächlich mit Systemressourcen. Daher können solche Funktionen von jedem Datenbankbenutzer erstellt werden, etwas verderben und er wird nicht in der Lage sein, zu viel zu lernen. Funktionen in UNTRUSTED-Sprachen können nur von einem Supervisor erstellt werden.

Wenn der Sprachinterpreter solche Einschränkungen unterstützt, können damit sowohl vertrauenswürdige als auch nicht vertrauenswürdige Sprachen erstellt werden. Also mit Perl gibt es also verschiedene Sprachen plperlund plperlu. Buchstabe uam Ende gibt es den nicht vertrauenswürdigen Charakter der Sprache aus. Python existiert nur in einer nicht vertrauenswürdigen Version. PL / v8 - im Gegenteil, nur in vertrauenswürdigen. Infolgedessen kann PL / v8 keine Module oder Bibliotheken von der Festplatte laden, sondern nur von der Datenbank.

Eine Funktion in der Sprache UNTRUSTED kann alles: Senden einer E-Mail, Pingen einer Site, Anmelden bei einer fremden Datenbank und Ausführen einer HTTP-Anforderung. VERTRAUENE Sprachen beschränken sich auf die Verarbeitung von Daten aus der Datenbank.

Zu VERTRAUEN gehören : plpgsql, plperl, plv8, pljava.

Von UNTRUSTED gehören : plperlu, pljavau, plpython2u, plpython3u.

Bitte beachten Sie: Es gibt kein PL / Python wie TRUSTED (da Sie dort keine Einschränkungen für den Zugriff auf Ressourcen festlegen können), und PLpgSQL und PL / V8 sind umgekehrt: Sie sind nicht UNTRUSTED.

Perl und Java sind jedoch in beiden Versionen verfügbar.

PL / pgSQL vs PL / *


PL / pgSQL-Code funktioniert nativ mit allen Datentypen, die Postgres hat. Andere Sprachen haben nicht viele Postgres-Typen, und der Sprachinterpreter sorgt dafür, dass die Daten in eine interne Darstellung der Sprache konvertiert werden und obskure Typen durch Text ersetzt werden. Ihm kann jedoch mit Hilfe von TRANSFORM geholfen werden, worüber ich am Ende des Artikels sprechen werde.

Funktionsaufrufe in PL / pgSQL sind oft teurer. Funktionen in anderen Sprachen können auf ihre Bibliotheken zugreifen, ohne den Systemkatalog zu lesen. PL / pgSQL kann so nicht funktionieren. Einige Abfragen in PL / pgSQL funktionieren lange Zeit, da viele Typen unterstützt werden: Um zwei Ganzzahlen hinzuzufügen, muss der Interpreter erkennen, dass es sich um Ganzzahlen und nicht um einige andere exotische Typen handelt, und dann entscheiden wie man sie faltet und erst danach faltet man sie tatsächlich.

Da PL / pgSQL VERTRAUEN ist, können Sie nicht mit dem Netzwerk und den Festplatten davon arbeiten.

Wenn es um die Arbeit mit verschachtelten Datenstrukturen geht, verfügt PL / pgSQL nur über Postgres-Tools für die Arbeit mit JSON, die sehr umständlich und unproduktiv sind. In anderen Sprachen ist die Arbeit mit verschachtelten Strukturen viel einfacher und wirtschaftlicher.

PL / * verfügt über eine eigene Speicherverwaltung, und Sie müssen den Speicher überwachen oder möglicherweise einschränken.

Sie sollten die Fehlerbehandlung sorgfältig überwachen, die auch für alle unterschiedlich ist.

In PL / * gibt es jedoch einen globalen Interpreter-Kontext, der beispielsweise zum Zwischenspeichern von Daten einschließlich Abfrageplänen verwendet werden kann. Wenn die Sprache NICHT VERTRAUEN ist, sind das Netzwerk und die Laufwerke verfügbar. Alle diese Sprachen arbeiten in der Regel über das SPI mit der Datenbank, aber dazu später mehr.

Lassen Sie uns die Funktionen der PL / * -Sprachen genauer betrachten.

PL / Perl


Der Perl-Interpreter ist ein umfangreicher Code im Speicher, der jedoch glücklicherweise nicht beim Öffnen der Verbindung erstellt wird, sondern nur beim Starten der ersten gespeicherten Prozedur / Funktion PL / Perl. Bei der Initialisierung wird der in den Postgres-Konfigurationsparametern angegebene Code ausgeführt. Normalerweise werden Module geladen und Vorberechnungen durchgeführt. Wenn Sie der Konfigurationsdatei hinzugefügt haben, während die Datenbank ausgeführt wird, lassen Sie Postgres die Konfiguration erneut lesen. In diesem Artikel verwenden die Beispiele ein Modul zur Visualisierung von Datenstrukturen. Es gibt Parameter für die separate Initialisierung von TRUSTED und UNTRUSTED Perl und natürlich einen Parameter . Diejenigen, die in Perl programmieren, wissen, dass ohne Perl keine Sprache, sondern ein Missverständnis ist.

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


Darin wird der Interpreter beim ersten Zugriff auf dieselbe Weise erstellt. Und hier ist es wichtig, sofort zu entscheiden, welche Python Sie wollen: zweite oder dritte. Wie Sie wissen, gibt es Python in zwei gängigen Versionen (Python 2 und Python 3), aber das Problem ist, dass ihre So-Shki nicht in einem Prozess miteinander auskommen: Es gibt einen Namenskonflikt. Wenn Sie in einer Sitzung mit v2 gearbeitet und dann v3 aufgerufen haben, stürzt Postgres ab, und für den Serverprozess (Backend) ist dies ein schwerwiegender Fehler. Um auf eine andere Version zuzugreifen, müssen Sie eine andere Sitzung öffnen.

Im Gegensatz zu Perl kann Python während der Initialisierung nicht gesagt werden, was zu tun ist. Eine weitere Unannehmlichkeit: Einzeiler sind unpraktisch.

In allen Python-Funktionen sind zwei Wörterbücher definiert - statisch SDund global GD. Global erlaubtDatenaustausch mit allen Funktionen in einem Backend - was gleichzeitig attraktiv und gefährlich ist. Jede Funktion verfügt über ein statisches Wörterbuch.

In PL / Python können Sie Subtransaktionen durchführen, die im Folgenden erläutert werden.

PL / V8


Es ist nur vertrauenswürdig.

Praktischerweise werden JSON-Daten automatisch in eine JS-Struktur konvertiert. In PL / V8 können Sie wie in PL / Python Subtransaktionen durchführen. Es gibt eine Schnittstelle für vereinfachte Funktionsaufrufe. Dies ist die einzige fragliche prozedurale Sprache, in der Fensterfunktionen definiert werden können . Sie schlagen vor, dass sie in PL / R definiert werden können , aber diese Sprache liegt außerhalb des Geltungsbereichs dieses Artikels.

Und nur in PL / V8 gibt es ein Ausführungszeitlimit. Richtig, es ist nicht standardmäßig aktiviert. Wenn Sie PL / V8 von Hand erstellen, müssen Sie angeben, dass es während der Assembly aktiviert wurde. Anschließend können Sie mit dem Konfigurationsparameter Zeitüberschreitungen für Funktionsaufrufe festlegen.

Die Initialisierung in PL / V8 sieht interessant aus: Da es vertrauenswürdig ist, kann es die Bibliothek nicht von der Festplatte lesen und nichts von irgendwoher laden. Er kann alles, was er braucht, nur von der Basis nehmen. Daher wird eine gespeicherte Initialisierungsfunktion definiert, die beim Start des Sprachinterpreters aufgerufen wird. Der Funktionsname wird in einem speziellen Konfigurationsparameter angegeben:

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

Während der Initialisierung können globale Variablen und Funktionen erstellt werden, indem ihre Werte den Attributen dieser Variablen zugewiesen werden. Zum Beispiel so:

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() );
$$;

Vergleich von PL / Perl gegen PL / Python gegen PL / V8 in der Praxis


Hallo Welt!


Lassen Sie uns eine einfache Übung mit der Ausgabe dieser Phrase in allen drei Sprachen durchführen, zuerst in PL / Perl . Und lassen Sie ihn etwas anderes Nützliches tun, zum Beispiel, sagt seine Version:

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

NOTICE:  Hello World!
DO

Sie können auch die üblichen Perl-Funktionen warnund verwenden die.

Jetzt in PL / Python . Genauer gesagt auf PL / Python3u (nicht vertrauenswürdig) - für die Bestimmtheit.

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

Kann verwenden throw 'Errmsg'. Es gibt viele Dinge, die Sie aus Postgres-Nachrichten extrahieren können: Sie enthalten Hinweis, Details, Zeilennummer und viele andere Parameter. In PL / Python können sie übergeben werden, jedoch nicht in den anderen betrachteten Sprachen: Ihre Mittel können nur mit einer einfachen Textzeile verflucht werden.

In PL / Python hat jede Postgres-Protokollierungsstufe ihre eigene Funktion: HINWEIS, WARNUNG, DEBUG, LOG, INFO, FATAL. Wenn es sich um ERROR handelt, ist die Transaktion gefallen. Wenn FATAL, ist das gesamte Backend gefallen. Glücklicherweise erreichte die Angelegenheit PANIC nicht. Sie können hier lesen .

PL / V8

In dieser Sprache ist Hello World Pearl sehr ähnlich. Sie können die exceptionVerwendung beenden throw, und dies ist auch eine Fehlerbehandlung, obwohl die Tools nicht so fortgeschritten sind wie in Python. Wenn du schreibstplv8.elog(ERROR)Der Effekt wird übrigens der gleiche sein.

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

NOTICE:  Hello World! 2.3.14
DO

Arbeite mit der Basis


Nun wollen wir sehen, wie man mit einer Datenbank aus gespeicherten Prozeduren arbeitet. Postgres verfügt über eine SPI (Server Programming Interface). Dies ist eine Reihe von C-Funktionen, die allen Autoren von Erweiterungen zur Verfügung stehen. Fast alle PL-Sprachen bieten Wrapper für SPI, aber jede Sprache macht es ein bisschen anders.

Eine in C geschriebene Funktion, die jedoch SPI verwendet, bietet im Vergleich zu PL / PgSQL und anderen prozeduralen Sprachen wahrscheinlich keine signifikanten Vorteile. Eine C-Funktion, die SPI umgeht und beispielsweise mit Daten ohne Zwischenhändler arbeitet, arbeitet table_beginscan/heap_getnextjedoch um eine Größenordnung schneller.

PL / Java verwendet auch SPI. Die Arbeit mit der Datenbank erfolgt jedoch weiterhin im Stil von JDBC und des JDBC-Standards. Für den Ersteller von Code in PL / Java geschieht alles so, als würden Sie von einer Clientanwendung aus arbeiten, aber JNI (Java Native Interface) übersetzt Aufrufe an die Datenbank in dieselben SPI-Funktionen. Es ist praktisch und es gibt keine grundlegenden Hindernisse, um dieses Prinzip in PL / Perl und PL / Python zu übersetzen, aber aus irgendeinem Grund wurde dies nicht getan und ist bisher in den Plänen nicht sichtbar.

Wenn Sie möchten, können Sie natürlich auf die übliche Weise zu ausländischen Stützpunkten gehen - über DBI oder Psycopg . Es ist möglich, lokale Datenbank, aber warum.

Wenn Sie sich nicht mit dem ganzheitlichen Thema "Prozess in der Basis vs. Prozess auf dem Client" befassen und sofort von der maximalen Verarbeitung näher an den Daten fortfahren (zumindest, um keine riesigen Proben über das Netzwerk zu übertragen), ist die Lösung für die Verwendung der auf dem Server gespeicherten Funktionen eine gute Wahl natürlich.

Leistung : Beachten Sie, dass SPI einen gewissen Overhead hat und SQL-Abfragen in Funktionen möglicherweise langsamer sind als ohne Funktionen. Das 13. Postgres enthielt einen Patch von Konstantin Knizhnik , der diese Kosten reduziert. Die Verarbeitung von Abfrageergebnissen in einer gespeicherten Funktion erfordert jedoch natürlich nicht die Übertragung des Ergebnisses an den Client und kann daher hinsichtlich der Leistung vorteilhaft sein.

Sicherheit: Eine Reihe von debuggten und getesteten Funktionen isoliert die Datenbankstruktur vom Benutzer und schützt vor SQL-Injektionen und anderem Unfug. Andernfalls bleibt es jedem Anwendungsentwickler Kopfschmerzen. Wiederverwendung von

Code : Wenn eine große Anzahl komplexer Anwendungen mit der Datenbank arbeiten, ist es praktisch, nützliche Funktionen auf dem Server zu speichern, anstatt sie in jeder Anwendung erneut zu schreiben.

Wie und in welcher Form erhalten wir Daten aus der Datenbank


In Perl ist alles einfach und klar. Der Aufruf spi_exec_querygibt die Anzahl der verarbeiteten Zeilen, den Status und das von der SQL-Abfrage ausgewählte Zeilenarray zurück:

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'
        };

In Python sehen die Abfrage und das Ergebnis ungefähr so ​​aus, aber hier gibt die Funktion keine Datenstruktur zurück, sondern ein spezielles Objekt, mit dem Sie auf verschiedene Arten arbeiten können. Normalerweise gibt es vor, ein Array zu sein, und dementsprechend können Sie Zeichenfolgen daraus extrahieren.

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

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

Und jetzt nehmen wir die erste Zeile, verlassen X und erhalten den Wert - die Zahl.

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

NOTICE:  57
DO

In PL / V8 :

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

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

Um die Struktur zu sehen, haben wir die Bibliotheksfunktion JSON.stringify verwendet, die nicht speziell geladen werden muss. Sie ist standardmäßig bereits als Teil von PL / v8 einsatzbereit.

Abschirmung


Um böswillige SQL-Injektionen zu vermeiden, müssen einige Zeichen in Abfragen maskiert werden. Dazu gibt es zum einen SPI-Funktionen und entsprechende Funktionen (in C geschrieben) in Sprachen, die wie SPI-Wrapper funktionieren. Zum Beispiel in PL / Perl:

quote_literal- nimmt Apostrophe auf und verdoppelt 'und \. Entwickelt für das Screening von Textdaten.
quote_nullable- gleich, aber undefin NULL konvertiert.
quote_ident- zitiert gegebenenfalls den Namen der Tabelle oder des Feldes. Nützlich, wenn Sie eine SQL-Abfrage erstellen und die Namen der darin enthaltenen Datenbankobjekte ersetzen.

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

Beachten Sie: Der Tabellenname darf nicht wie eine Textzeile maskiert werden. Deshalb gibt es eine Funktion quote_ident.

In PL / Perl gibt es jedoch andere Funktionen zum Abschirmen von Daten einzelner Post-Gres-Typen: Eine Funktion sollte jeden Typ akzeptieren und atypische zweifelhafte Zeichen in etwas offensichtlich Sicheres verwandeln. Es funktioniert mit einer Vielzahl von Typen, aber dennoch nicht mit allen. Sie wird beispielsweise Bereichstypen nicht verstehen und sie einfach als Textzeichenfolgen wahrnehmen.

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

In PL / Python gibt es drei ähnliche Funktionen, die auf die gleiche Weise funktionieren:

plpy.quote_literal
plpy.quote_nullable
plpy.quote_ident


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

Sind die Funktionen in PL / V8 gleich ?

Na sicher! Bis auf syntaktische Merkmale ist alles gleich.

plv8.quote_literal
plv8.quote_nullable
plv8.quote_ident


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

NOTICE:  'Macy''s'

Performance


Welche Sprache ist die schnellste? Normalerweise antworten sie: C. Aber die richtige Antwort ist C oder SQL. Warum SQL? Tatsache ist, dass eine Funktion in dieser Sprache nicht immer explizit ausgeführt wird. Es kann in die Anforderung eingebettet werden (der Scheduler bettet die Funktion in den Hauptteil der Hauptanforderung ein), kann mit der Anforderung gut optimiert werden und das Ergebnis ist schneller. Aber unter welchen Bedingungen kann Code in eine Anfrage eingebettet werden? Es gibt ein paar einfache Bedingungen , dass Sie lesen können, sagen wir hier . Beispielsweise sollte eine Funktion nicht mit den Rechten des Eigentümers ausgeführt werden (um SECURITY DEFINER zu sein). Die meisten einfachen Funktionen passen zu diesen Bedingungen.

In diesem Artikel werden wir "am Knie" messen, nicht ernsthaft. Wir brauchen einen groben Vergleich. Schalten Sie zuerst das Timing ein:

\timing

Versuchen wir es mit SQL (Die Ausführungszeiten der folgenden Befehle sind die gerundeten Durchschnittswerte, die der Autor auf einem entladenen sechs Jahre alten PC erhalten hat. Sie können miteinander verglichen werden, erheben jedoch keinen Anspruch auf Wissenschaftlichkeit):

SELECT count(*) FROM pg_class;
0.5 ms

Es funktioniert sehr schnell. In anderen Sprachen wird Zeit damit verschwendet, Funktionen aus der Sprache aufzurufen. Natürlich wird die Anforderung beim ersten Mal aufgrund der Initialisierung des Interpreters langsamer ausgeführt. Dann stabilisiert es sich.

Versuchen wir es mit 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

Es war Python 2. Jetzt Python 3 (Rückruf: Python2 und Python3 leben nicht friedlich in derselben Sitzung, ein Namenskonflikt ist möglich):

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

Und schließlich PL / V8 :

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

Aber es ist irgendwie sehr schnell. Versuchen wir, die Abfrage 1000 Mal oder 1 Million Mal auszuführen. Plötzlich wird der Unterschied deutlicher:

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

Beachten Sie, dass das Experiment mit PL / V8 mit tausend und nicht mit einer Million Iterationen durchgeführt wurde. Mit moderaten Ressourcen wird der PL / V8 in einem Zyklus von 1 Million Operationen den gesamten Speicher verbrauchen und das Auto vollständig aufhängen. Bereits bei tausend Iterationen wählt der Postgres-Prozess 3,5 GB Speicher und 100% Schreibzugriff auf die Festplatte. Tatsächlich startet postgres die V8-Umgebung und frisst natürlich Speicher. Nach Ausführung der Anfrage wird dieses Turbomonster keinen Speicher mehr zurückgeben. Um Speicher freizugeben, müssen Sie die Sitzung beenden.

Wir sehen, dass PL / pgSQL bereits zweimal schneller ist als PL / Perl und PL / Python. PL / V8 ist immer noch etwas dahinter, aber gegen Ende des Artikels wird er teilweise rehabilitiert.

Im Allgemeinen zeigen Perl mit Python in diesen Experimenten ungefähr die gleichen Ergebnisse. Perl war Python früher etwas unterlegen, in modernen Versionen ist es etwas schneller. Die dritte Python ist etwas langsamer als die zweite. Der gesamte Unterschied liegt innerhalb von 15%.

Leistung mit PREPARE


Leute, die es wissen, werden verstehen: etwas stimmt nicht. PL / pgSQL kann Abfragepläne automatisch zwischenspeichern und in PL / * jedes Mal, wenn die Abfrage erneut geplant wurde. Auf eine gute Weise müssen Sie Anforderungen vorbereiten, einen Anforderungsplan erstellen und dann gemäß diesem Plan so oft wie nötig ausgeführt werden. In PL / * können Sie explizit mit Abfrageplänen arbeiten, die wir ab PL / Perl versuchen werden :

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

Mit prepareunseren beiden Sprachen haben wir PL / pgSQL fast eingeholt, während die dritte ebenfalls wollte, aber aufgrund der wachsenden Nachfrage nach Speicher nicht die Ziellinie erreichte.

Aber wenn Sie das Gedächtnis nicht berücksichtigen, ist es klar, dass alle Sprachen fast Kopf an Kopf gehen - und nicht zufällig. Ihr Engpass ist mittlerweile weit verbreitet - die Arbeit mit der Datenbank über SPI.

Rechenleistung


Wir sehen, dass die Sprachleistung in der Arbeit mit der Datenbank ruht. Um Sprachen miteinander zu vergleichen, versuchen wir, etwas zu berechnen, ohne auf die Datenbank zurückzugreifen, z. B. die Summe der Quadrate.

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

Wir sehen, dass PL / Perl und PL / Python PL / pgSQL überholt und überholt haben, sie sind viermal schneller. Und die Acht zerreißt alle! Aber ist es wirklich umsonst? Oder bekommen wir es für den Kopf? Ja wir werden.

Die Zahl in JavaScript ist ein Float und das Ergebnis ist schnell, aber nicht genau: 333333833333127550 anstelle von 333333833333500000.

Hier ist die Formel, nach der das genaue Ergebnis berechnet wird :

∑ = n*(n+1)*(2n+1)/6

Als Übung können Sie dies durch mathematische Induktion beweisen.

In der Reihenfolge des Lachens

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

NOTICE:
33333383333312754000

In Javascript wird parseIntimmer noch ein Float ausgeführt, kein Int.

Trotzdem erschien BigInt 2018 in V8 , und es kann jetzt sicher gezählt werden, jedoch mit einem Nachteil für die Geschwindigkeit, da es sich nicht um eine 64-Bit-Ganzzahl handelt, sondern um eine Ganzzahl mit beliebiger Bittiefe. In PL / V8 hat diese Innovation jedoch noch nicht getroffen. In anderen prozeduralen Sprachen werden beliebige Bitnummern (Analoga von SQL numeric) durch spezielle Bibliotheken unterstützt.

In Perl gibt es ein Math :: BigFloat-Modul für Arithmetik mit beliebiger Genauigkeit, und in Python ist das Bigfloat- Paket ein Cython- Wrapper um die GNU MPFR- Bibliothek .

Leistungsfunktionen zum Sortieren


Hier ist ein praktisches Beispiel, das den Unterschied in der Leistung der Sortierung nach Funktionen zeigt, wenn diese Funktion in verschiedenen Sprachen geschrieben ist. Aufgabe: Sortieren der Textfelder mit den Nummern der Ausgaben des Journals, die wie folgt lauten können:

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

Jene. Es ist eigentlich eine Zeichenfolge, aber sie beginnt mit einer Zahl, und Sie müssen nach diesen Zahlen sortieren. Um korrekt als Zeichenfolgen zu sortieren, ergänzen wir den numerischen Teil links mit Nullen, um Folgendes zu erhalten:

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

Ja, ich weiß, dass dies nicht die einzige Lösung für das Problem ist (und nicht einmal ganz richtig). Aber zum Beispiel wird es reichen.

Um einen Typ anzufordern, SELECT ... ORDER BY nsort(n)schreiben wir Funktionen in PL / Perl, SQL, PL / Python und PL / V8, die die Journalnummern in diese Form konvertieren:

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; } 
$$;

In meiner Bibliothek mit 15,5 Tausend Zeitschriftenartikeln dauert eine Abfrage mit einer Funktion in PL / Perl ungefähr 64 ms gegenüber 120 ms in PL / Python und 200 ms in PL / PgSQL. Aber das schnellste - PL / v8: 54ms.

Hinweis: Wenn Sie mit dem Sortieren experimentieren, stellen Sie die erforderliche Menge an Arbeitsspeicher bereit, damit die Sortierung im Speicher abläuft (EXPLAIN wird dann angezeigt Sort Method: quicksort). Die Speichermenge wird durch den Parameter eingestellt work_mem:

set work_mem = '20MB';

Erinnerung


Perl mag keine geschlungenen Strukturen, er weiß nicht, wie man sie reinigt. Wenn Sie aeinen Zeiger auf bund einen bZeiger auf haben a, wird der Referenzzähler niemals zurückgesetzt und der Speicher wird nicht freigegeben.

Garbage Collection-Sprachen haben andere Probleme. Es ist beispielsweise nicht bekannt, wann Speicher freigegeben wird oder ob er überhaupt freigegeben wird. Oder - wenn Sie sich nicht absichtlich darum kümmern - Sammler werden im ungünstigsten Moment Müll sammeln.

Es gibt aber auch Speicherverwaltungsfunktionen, die in direktem Zusammenhang mit Postgres stehen. Es gibt Strukturen, die SPI zuweist, und Perl erkennt nicht immer, dass sie freigegeben werden müssen.

PL / Perl

So geht es NICHT:

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

Und so geht es:

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};
$$;

Nach der Hinrichtung $hbleibt der Handler am Leben, obwohl keine einzige lebende Verbindung zu ihm besteht.

Es ist in Ordnung, Sie müssen sich nur an die Notwendigkeit erinnern, Ressourcen explizit freizugeben mit 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 fließt nie , der Plan wird automatisch freigegeben:

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

PL / V8

Gleiche Geschichte wie Perl. Es fließt nicht so:

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

Und so geht es:

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

Nochmals: Vergessen Sie nicht, Ressourcen freizugeben. Hier tut es. h.free();

Es fließt nicht:

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


Es ist Zeit zu verstehen, wie Argumente an Funktionen übergeben werden. In den Beispielen übergeben wir 4 Parameter mit Typen an die Funktion:

  • ganze;
  • eine Anordnung;
  • bytea und
  • jsonb

Wie kommen sie zu 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)

Wird es JSON oder JSONB sein - in diesem Fall macht es keinen Unterschied: Sie erhalten immer noch die Form eines Strings. Dies ist eine Gebühr für die Vielseitigkeit: Postgres hat viele Typen mit unterschiedlichem Grad an "eingebaut". Vom Entwickler zu verlangen, dass er mit dem neuen Typ sofort Konvertierungsfunktionen für alle PL / * bereitstellt, wäre zu viel. Standardmäßig werden viele Typen als Zeichenfolgen übergeben. Dies ist jedoch nicht immer praktisch. Sie müssen diese Begriffe analysieren. Natürlich möchte ich, dass Postgres-Daten sofort in die entsprechenden Perl-Strukturen umgewandelt werden. Standardmäßig ist dies nicht der Fall, aber ab 9.6 wurde der TRANSFORM-Mechanismus angezeigt - die Möglichkeit, Typkonvertierungsfunktionen zu definieren: CREATE TRANSFORM .

Um TRANSFORM zu erstellen, müssen Sie zwei Funktionen in C schreiben: Eine konvertiert Daten eines bestimmten Typs auf die eine Seite, die andere zurück. Bitte beachten Sie, dass TRANSFORM an vier Stellen funktioniert:

  • Beim Übergeben von Parametern an eine Funktion;
  • Bei Rückgabe eines Funktionswertes;
  • Beim Übergeben von Parametern an einen SPI-Aufruf innerhalb einer Funktion;
  • Nach Erhalt des Ergebnisses des SPI-Aufrufs innerhalb der Funktion.

TRANSFORM JSONB für Perl und Python, entwickelt von Anton Bykov, erschien in der 11. Version von Postgres. Jetzt müssen Sie JSONB nicht mehr analysieren, es wird sofort als entsprechende Struktur in Perl übertragen. Sie müssen die Erweiterung jsonb_plperl erstellen und können dann TRANSFORM verwenden:

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

Sie können diese Funktion aufrufen, um zu überprüfen, ob JSONB zu einem Perlen-Hash geworden ist:

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


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

Eine ganz andere Sache!

Der Autor dieses Artikels war auch an der Entwicklung von TRANSFORMs beteiligt. Es stellte sich heraus, dass ein so einfacher Datentyp, wie er booleanin einer unbequemen Form an PL / Perl übergeben wurde, als Textzeichenfolgen 't'oder 'f'. Aber nach Perls Verständnis ist die Zeichenfolge 'f' wahr. Um die Unannehmlichkeiten zu beseitigen, wurde ein Patch erfunden , der die Konvertierung für den Booleschen Typ definiert . Dieser Patch hat PostgreSQL 13 erreicht und wird in Kürze verfügbar sein. Aufgrund seiner Einfachheit kann bool_plperl als minimales Startmodell für das Schreiben anderer Konvertierungen dienen.

Ich hoffe, jemand wird TRANSFORM für andere Datentypen (Bytea, Arrays, Datumsangaben, numerisch) entwickeln.

Nun wollen wir sehen, wie die Parameter in Python übergeben werden .

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)

Ein Array wird in ein Array konvertiert - das ist gut (da mehrdimensionale Arrays der Version PG10 auch korrekt in Python übertragen werden). In Perl wurde ein Array in ein Objekt einer speziellen Klasse konvertiert. Nun, jsonbverwandelt. Ohne TRANSFORM wird jsonb als String übergeben.

Nun wollen wir sehen, in welcher Form die Parameter in JS gelangen .

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 wurde ohne TRANSFORM in ein JavaScript-Objekt konvertiert! Temporäre Postgres-Typen werden ebenfalls in den Datums-JS-Typ konvertiert. Gleiches gilt für Boolesche Werte. Alle Transformationen sind bereits in PL / V8 integriert.

Arbeite mit Unendlichkeit


Die INFINITY-Konstante wird nicht sehr oft verwendet, aber schlampiges Arbeiten damit ist gefährlich. In PostgreSQL existieren Infinity und -Infinity als spezielle Werte für einige temporäre und Gleitkommatypen. Die Übertragung von Infinity in prozedurale Sprachen und umgekehrt muss jedoch ausführlich erörtert werden, da die Arbeit mit ihnen nicht nur von der Sprache, sondern auch von Bibliotheken, vom Betriebssystem und sogar von der Hardware abhängen kann.

Python hat ein Numpy-Modul, das die numerische Unendlichkeit definiert:

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

inf -inf

Perl hat auch unendlich, es verwendet eine Zeichenfolge "infinity", die verkürzt werden kann "inf". Zum Beispiel könnte man sagen:

perl -e 'print 1 * "inf"'

Inf

oder

perl -e 'print 1/"inf"'

0

In PL / Perl, PL / Python, PL / v8 wird die numerische Unendlichkeit von Postgres korrekt übergeben, aber ein unendliches Datum ist nicht ganz richtig. Vielmehr gibt es in PL / Perl und PL / Python keinen eingebauten Datentyp für die Zeit, sondern eine Zeichenfolge. In PL / V8 ist ein Datum vom Typ integriert, aus dem das übliche Datum eines Postgres wird. Aber der V8 kennt das endlose Datum nicht und wenn es übertragen wird, verwandelt es sich in Invalid Date.

Übergabe von Parametern an vorbereitete Anforderungen


Zurück zu prepare, überlegen Sie, wie die Parameter dort übergeben werden. Verschiedene Sprachen haben viel gemeinsam, da sie alle auf SPI basieren.

Wenn Sie eine Abfrage in PL / Perl vorbereiten , müssen Sie den Typ der übergebenen Parameter bestimmen. Wenn Sie die Abfrage ausführen, geben Sie nur die Werte dieser Parameter an (die Parameter werden auf dieselbe Weise an PL / pgSQL übergeben).

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);
$$;

In PL / Python ist die Essenz dieselbe, aber die Syntax unterscheidet sich geringfügig:

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

In PL / V8 sind die Unterschiede 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();
$$;

In PL / Java ist alles anders. Dort wird SPI eindeutig nicht verwendet, es wird jedoch eine Pseudo-JDBC-Verbindung zur Datenbank hergestellt. Für einen PL / Java-Programmierer geschieht alles so, als würde er eine Clientanwendung erstellen. Dies ist praktisch und man könnte sich auch dem Design von PL / Perl und PL / Python nähern, aber aus irgendeinem Grund wurde dies nicht getan (jedoch verbietet niemand, ein paar weitere Implementierungen von PL / Perl und PL / Python zu erstellen).

Mit dem Cursor arbeiten


Alle SPI-Funktionen, die wir beim Aufrufen der Datenbank verwendet haben - spi_exec_query()und andere - haben einen Parameter, der die Anzahl der zurückgegebenen Zeilen begrenzt. Wenn Sie viele zurückgegebene Zeilen benötigen, können Sie nicht auf einen Cursor verzichten, um sie ein wenig nach oben zu ziehen.

Cursor arbeiten in all diesen Sprachen. Gibt in PL / Perl
spi_exec_query einen Cursor zurück, aus dem Sie nacheinander Zeichenfolgen extrahieren können. Es ist nicht erforderlich, den Cursor zu schließen, er schließt sich von selbst. Wenn Sie es jedoch erneut entdecken möchten, können Sie es explizit mit einem Befehl schließen 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.
...

In PL / Python ist alles sehr ähnlich, aber der Cursor wird als Objekt dargestellt, durch das Sie blättern können:

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

In PL / v8 ist auch alles sehr ähnlich, aber vergessen Sie nicht, den vorbereiteten Abfrageplan freizugeben :

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

PL / V8: Schneller Zugriff auf Funktionen


In PL / V8 können Sie eine Funktion nicht von einem regulären SELECT aus aufrufen, sondern anhand des Namens suchen und sofort mit starten plv8.find_function(name);. Beachten Sie jedoch, dass in JS eine Funktion nicht polymorph sein kann, wie in PostgreSQL, in dem Funktionen mit demselben Namen, aber unterschiedlichen Parametern nebeneinander existieren können. In PL / v8 können wir natürlich polymorphe Funktionen erstellen, aber beim Versuch, find_functionsie zu verwenden , tritt ein Fehler auf .

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

Wenn eine Funktion nach Namen eindeutig ist, kann sie ohne SPI- und Typkonvertierungen aufgerufen werden, d. H. viel schneller. Zum Beispiel so:

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

Transaktionen


Postgres 11 hat viel Spaß: Es sind echte Prozeduren erschienen . Postgres hatte früher nur Funktionen. Die Freude liegt nicht nur an der Kompatibilität und Konformität mit dem SQL-Standard, sondern auch daran, warum: Innerhalb der Prozeduren können Sie Transaktionen festschreiben und zurücksetzen.

PL / Perl und PL / Python verfügen bereits über SPI-Funktionen zum Verwalten von Transaktionen, PL / V8 noch nicht. In PL / Perl, werden diese Funktionen aufgerufen spi_commit()und spi_rollback(), und ein Beispiel für die Verwendung ist in der Dokumentation . In PL / Python ist dies plpy.commit()und plpy.rollback().

Subtransaktion


Subtransaktionen sind praktisch für die korrekte Fehlerbehandlung in komplexer mehrstufiger Logik.

In PL / pgSQL innerhalb einer Transaktion ist jeder Block mit dem Schlüsselwort EXCEPTION eine Subtransaktion. Informationen zu einigen Leistungs- und Zuverlässigkeitsproblemen, die in diesem Fall auftreten können, finden Sie beispielsweise hier .

Es gibt keine expliziten Subtransaktionen in PL / Perl , aber sie können durch Savaepoints simuliert werden. Wenn Sie möchten, ist es anscheinend einfach, ein Perlenmodul zu schreiben, das Subtransaktionen in expliziter Form implementiert.

In PL / Python sind Subtransaktionen vor langer Zeit aufgetreten: ab 9.5 explizit , davor gab es implizite . Sie können eine Transaktion definieren und einschließentry-und ausführen. Wenn die Subtransaktion abfällt, fallen wir in den Block except, wenn nicht abfallen, dann in den Block elseund gehen weiter.

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

Ein ähnliches Design gibt es in PL / V8 nur in JS-Syntax.

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

Fazit


Versuchen Sie es, aber missbrauchen Sie es nicht :) Kenntnisse in PL / * können einige Vorteile bringen. Wie jedes Werkzeug lieben sie es, für ihren beabsichtigten Zweck verwendet zu werden.

PL / v8 ist sehr vielversprechend, verhält sich aber manchmal unerwartet und weist eine Reihe von Problemen auf. Daher ist es besser, Sprachen aus der Verpackung zu nehmen, wenn sie für Ihre Aufgabe geeignet sind.

Ich möchte Igor Levshin danken (Igor_Le), der mir bei der Vorbereitung des Materials für den Artikel sehr geholfen und einige nützliche Ideen eingebracht hat, sowie Jewgeni Sergejew und Alexey Fadeev für die von ihnen vorgeschlagenen Korrekturen.

All Articles