PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Panoramica della programmazione lato server in PostgreSQL

Ci sono molti modi in cui puoi far eseguire al server Postgres un codice predefinito. Di seguito è riportato un elenco completo, con esempi, dei modi in cui puoi lasciare che il server Postgres memorizzi una logica predefinita, che puoi utilizzare in seguito dalla tua applicazione.

Funzioni SQL

Postgres ti consente di creare "funzioni definite dall'utente", in cui il corpo della funzione può essere scritto in una lingua supportata. Le "funzioni SQL" sono funzioni definite dall'utente scritte in SQL normale, che è il modo più semplice per incapsulare query complesse e sequenze di istruzioni SQL.

Ecco un paio di esempi:

-- update item price and record the change
CREATE FUNCTION update_price(item text, newprice numeric) RETURNS void AS $$
    UPDATE items SET price=$2 WHERE name=$1;
    INSERT INTO audit (event, new_price, at, item)
      VALUES ('price changed', $2, now(), $1);
$$ LANGUAGE SQL;

-- a function from uuid-osp
CREATE FUNCTION uuid_timestamp_bits(uuid) RETURNS varbit AS
$$ SELECT ('x' || substr($1::text, 15, 4) || substr($1::text, 10, 4) ||
           substr($1::text, 1, 8) || substr($1::text, 20, 4))::bit(80)
          & x'0FFFFFFFFFFFFFFF3FFF' $$
LANGUAGE SQL STRICT IMMUTABLE;

Le funzioni SQL possono accettare e restituire tipi di base, tipi compositi e righe. Supportano anche numeri variabili di argomenti, valori predefiniti per argomenti e argomenti polimorfici. Possono anche restituire più righe, imitando un SELECT da una tabella. Inoltre, non è necessario che restituiscano nulla.

Tuttavia, il corpo della funzione può contenere solo istruzioni SQL. Ciò significa che non ci sono istruzioni di controllo del flusso (if, while, …), variabili e simili.

Il comando CREATE FUNCTION viene utilizzato per creare la funzione. Come al solito, puoi ALTER e DROP.

Questo è un ottimo posto per iniziare a scavare ulteriormente:https://www.postgresql.org/docs/current/xfunc-sql.html

Funzioni C

Mentre le funzioni SQL sono le più facili da scrivere e meno potenti, all'altro capo dello spettro, le funzioni possono essere scritte in C e possono praticamente fare qualsiasi cosa. Tali funzioni devono essere codificate in C e costruite come una libreria condivisa che può essere caricata dinamicamente da Postgres.

Devi dire a Postgres dove caricare la libreria condivisa, il nome e la firma della funzione:

CREATE FUNCTION sum(integer, integer) RETURNS integer
    AS 'myfuncs', 'sum'
    LANGUAGE C STRICT;

Questo dice che la libreria condivisa myfuncs.so , presente in un percorso di ricerca predefinito, contiene punti di ingresso che possono essere richiamati da Postgres, con uno dei punti di ingresso "somma" che può essere invocata come funzione.

Il codice effettivo in C sarebbe troppo lungo per essere incluso qui, ma puoi leggere tutto al riguardo nei documenti. Combinato con l'interfaccia di programmazione del server (SPI), è possibile eseguire quasi tutte le operazioni che puoi eseguire in qualsiasi altro modo.

Ad esempio, con le funzioni C definite qui, puoi eseguire richieste HTTP:

SELECT status, content_type FROM http_get('https://postgresql.org/');

È anche possibile scrivere tali librerie condivise in altri linguaggi come C++ o Go, che possono creare librerie condivise con collegamenti "C".

Funzioni PL/pgSQL

Oltre a SQL e C, puoi scrivere funzioni in linguaggi procedurali . Quattro di questi linguaggi sono supportati da PostgreSQL di base:pgSQL, Python, Perl e Tcl. Il supporto per qualsiasi linguaggio procedurale stesso proviene da una libreria condivisa C e funziona in modo molto simile a mod_perl o mod_python dell'era Apache.

pgSQL è il linguaggio canonico, più utilizzato, simile a SQL in cui vengono scritte le funzioni memorizzate per PostgreSQL. È disponibile per impostazione predefinita, grazie al fatto che è installato in template1 .

PL/pgSQL è un linguaggio completo con variabili, espressioni e istruzioni di controllo; e include funzionalità come i cursori per lavorare con i dati SQL in particolare. È ampiamente documentato qui.

Ecco un esempio:

CREATE FUNCTION repeat(times integer, s text)
    RETURNS text
    AS $$
DECLARE
    result text;
BEGIN
    result := '';
    FOR i IN 1..times LOOP
        result := result || s;
    END LOOP;
    RETURN result;
END;
$$
LANGUAGE plpgsql
IMMUTABLE;

-- psql> SELECT repeat(10, '*');
--    repeat
-- ------------
--  **********
-- (1 row)

Altri linguaggi procedurali fondamentali

Gli altri linguaggi procedurali - Python, Perl, Tcl - consentono agli sviluppatori di utilizzare un linguaggio con cui sono già a proprio agio. Sebbene il supporto per questi linguaggi sia presente nell'albero dei sorgenti di Postgres, le distribuzioni di solito non installano i binari per impostazione predefinita. Ad esempio, in Debian potresti dover fare:

sudo apt install postgresql-plpython-11

per installare il supporto PL/Python per PostgreSQL 11.

Qualunque sia la lingua che stai utilizzando per scrivere una funzione, il chiamante non percepisce alcuna differenza nel suo utilizzo.

Pitone

L'estensione PL/Python supporta la scrittura di funzioni in Python 2 e Python 3. Per installarla, procedere come segue:

CREATE EXTENSION plpythonu;

Ecco una funzione scritta in PL/Python:

CREATE FUNCTION pymax (a integer, b integer)
  RETURNS integer
AS $$
  if a > b:
    return a
  return b
$$ LANGUAGE plpythonu;

L'ambiente Python in cui viene eseguito il corpo della funzione ha un modulo chiamato plpy importato automaticamente in esso. Questo modulo contiene metodi che ti consentono di preparare ed eseguire query, gestire transazioni e lavorare con i cursori.

Maggiori informazioni possono essere trovate nel capitolo 46 dei documenti di Postgres.

Perl

Ebbene sì, Perl. I processi di sviluppo, test e build di Postgres utilizzano Perlextensively ed è anche supportato come linguaggio procedurale. Per iniziare a usarlo, assicurati che tutti i pacchetti binari pertinenti per la tua distribuzione siano installati (esempio "postgresql-plperl-nn" per Debian) e installa l'estensione "plperl".

Ecco una funzione scritta in PL/Perl:

CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    my ($x, $y) = @_;
    if (not defined $x) {
        return undef if not defined $y;
        return $y;
    }
    return $x if not defined $y;
    return $x if $x > $y;
    return $y;
$$ LANGUAGE plperl;

Documentazione completa qui.

Tcl

Tcl è l'ennesimo PL supportato dal core Postgres. Ecco un esempio:

CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS $$
    if {[argisnull 1]} {
        if {[argisnull 2]} { return_null }
        return $2
    }
    if {[argisnull 2]} { return $1 }
    if {$1 > $2} {return $1}
    return $2
$$ LANGUAGE pltcl;

Per ulteriori informazioni, consulta i documenti qui.

Lingue procedurali non principali

Oltre a questi linguaggi, ci sono progetti open source che sviluppano e mantengono il supporto per altri come Java, Lua, R ecc.

C'è un elenco qui:https://www.postgresql.org/docs/current/external-pl.html

Funzioni aggregate

Le funzioni aggregate operano su un insieme di valori e restituiscono un singolo risultato. PostgreSQL ha un sacco di funzioni aggregate integrate (vedi un elenco completo qui). Ad esempio, per ottenere la deviazione standard della popolazione di tutti i valori in acolonna, può:

SELECT stddev_pop(grade) FROM students;

È possibile definire le proprie funzioni aggregate che si comportano in modo simile. Un aggregato definito dall'utente è assemblato da alcune singole funzioni autonome che operano sullo stato interno (ad esempio, lo stato interno di un aggregato che calcola la media potrebbe essere "somma" e "conteggio").

Ecco un aggregato definito dall'utente che calcola la mediana di un insieme di valori:

-- from https://wiki.postgresql.org/wiki/Aggregate_Median
CREATE OR REPLACE FUNCTION _final_median(NUMERIC[])
   RETURNS NUMERIC AS
$$
   SELECT AVG(val)
   FROM (
     SELECT val
     FROM unnest($1) val
     ORDER BY 1
     LIMIT  2 - MOD(array_upper($1, 1), 2)
     OFFSET CEIL(array_upper($1, 1) / 2.0) - 1
   ) sub;
$$
LANGUAGE 'sql' IMMUTABLE;
 
CREATE AGGREGATE median(NUMERIC) (
  SFUNC=array_append,
  STYPE=NUMERIC[],
  FINALFUNC=_final_median,
  INITCOND='{}'
);

che può essere invocato come:

SELECT median(grade) FROM students;

Per ulteriori informazioni, vedere i documenti sugli aggregati e l'istruzione CREATE AGGREGATE.

Tipi definiti dall'utente

Le librerie condivise scritte in C che abbiamo visto in precedenza non possono solo definire funzioni, ma anche tipi di dati. Questi tipi definiti dall'utente possono essere utilizzati come tipi di dati per le colonne, proprio come i tipi incorporati. Puoi definire funzioni per lavorare con i valori dei tuoi tipi definiti dall'utente.

Ci vuole un po' di codice per definire un nuovo tipo. Consulta i documenti qui che ti guidano attraverso la creazione di un nuovo tipo per rappresentare i numeri complessi. Il sorgente di Postgres contiene anche il codice tutorial per questo.

Operatori

Gli operatori semplificano l'utilizzo delle funzioni (ad esempio, scrivendo 1 + 2 anziché sum(1, 2) ), ed è possibile definire gli operatori per i tipi definiti dall'utente utilizzando l'istruzione CREATE OPERATOR.

Ecco un esempio per creare un + operatore che esegue il mapping alla funzionecomplex_add che aggiunge due complex numeri:

CREATE OPERATOR + (
    leftarg = complex,
    rightarg = complex,
    function = complex_add,
    commutator = +
);

Maggiori informazioni qui e qui.

Classi operatori e famiglie operatori

Le classi operatore consentono al tipo di dati di funzionare con il B-Tree integrato e altri metodi di indicizzazione. Ad esempio, se vuoi creare un indice B-Tree su una colonna di tipo "complesso", dovrai indicare a Postgres come confrontare due valori di questo tipo per determinare se uno è minore, uguale o maggiore dell'altro.

Sarebbe utile confrontare i tipi complessi con numeri interi o valori in virgola mobile, ed è qui che entrano in gioco le famiglie di operatori.

Puoi leggere tutto sulle classi e le famiglie degli operatori qui.

Trigger

I trigger sono un potente meccanismo per creare effetti collaterali per le normali operazioni, sebbene possano essere pericolosi se abusati o abusati. In sostanza, i trigger collegano gli eventi alle funzioni. La funzione a cui si fa riferimento può essere invocata:

  • prima o dopo l'inserimento/aggiornamento/cancellazione di una riga di una tabella
  • sul troncamento di una tabella
  • invece di inserire/aggiornare/eliminare una riga di una vista

La funzione può essere invocata per ogni riga interessata da un'istruzione o una volta perstatement. E ci sono ancora più cose, come la cascata di trigger, che sono tutte spiegate qui.

Le funzioni trigger possono essere scritte in C o in una qualsiasi delle funzioni PL, ma non in SQL. Ecco un esempio per inserire una riga in un audit tabella, per ogni aggiornamento effettuato al prezzo di un elemento .

-- first create the function
CREATE FUNCTION log_update() RETURNS TRIGGER AS $$
    INSERT INTO audit (event, new_price, at, item)
      VALUES ('price changed', NEW.price, now(), OLD.item);
$$
LANGUAGE plpgsql;

-- then create the trigger
CREATE TRIGGER audit_price_changes
    AFTER UPDATE ON items
    FOR EACH ROW
    WHEN (OLD.price IS DISTINCT FROM NEW.price)
    EXECUTE FUNCTION log_update();

Leggi tutto sui trigger qui, insieme alla documentazione CREATE TRIGGER.

Trigger di eventi

Mentre attiva rispondere a eventi DML su una singola tabella, trigger di eventi può rispondere a eventi DDL su un determinato database. Gli eventi includono la creazione, l'alterazione e il rilascio di una varietà di oggetti, come tabelle, indici, schemi, viste, funzioni, tipi, operatori, ecc.

Ecco un trigger di eventi che impedisce la caduta di oggetti dallo schema di "audit":

-- create function first
CREATE FUNCTION nodrop() RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
    IF EXISTS(
      SELECT 1
      FROM pg_event_trigger_dropped_objects() AS T
      WHERE T.schema_name = 'audit')
    THEN
      RAISE EXCEPTION 'not allowed to drop objects in audit schema';
    END IF;
END $$;

-- create event trigger
CREATE EVENT TRIGGER trigger_nodrop
    ON sql_drop
    EXECUTE FUNCTION nodrop();

Maggiori informazioni sono disponibili qui e nella documentazione CREATE EVENT TRIGGER.

Regole

PostgreSQL è dotato di una funzionalità che ti consente di riscrivere le query prima che arrivi al pianificatore di query. L'operazione è in qualche modo simile alla configurazione di Nginx o Apache per riscrivere un URL in ingresso prima di elaborarlo.

Ecco due esempi che influiscono sulle istruzioni INSERT su una tabella e le fanno fare qualcos'altro:

-- make inserts into "items" table a no-op
CREATE RULE rule1 AS ON INSERT TO items DO INSTEAD NOTHING;

-- make inserts go elsewhere
CREATE RULE rule2 AS ON INSERT TO items DO INSTEAD
    INSERT INTO items_pending_review VALUES (NEW.name, NEW.price);

Questo capitolo della documentazione contiene ulteriori informazioni sulle regole.

Procedure archiviate

A partire da Postgres 11, è possibile creare procedure memorizzate anche. Rispetto alle funzioni memorizzate, c'è solo una cosa in più che le procedure possono fare:il controllo delle transazioni.

Ecco un esempio:

CREATE PROCEDURE check_commit(v integer)
LANGUAGE plpgsql AS $$
BEGIN
    IF v % 2 = 0 THEN
        COMMIT;
    ELSE
        ROLLBACK;
    END IF;
END $$;

-- call it
CALL check_commit(10);

Vedi qui e qui per ulteriori informazioni.

Altre cose esotiche

Wrapper di dati stranieri

I Foreign Data Wrapper (FDW) ti consentono di comunicare con altre fonti di dati, come un altro server Postgres, MySQL, Oracle, Cassandra e altro ancora. Tutta la logica per l'accesso al server esterno è scritta in C, come libreria condivisa.

C'è anche un archivio colonnare chiamato cstore_fdw basato su FDW.

Puoi trovare un elenco di implementazioni FDW nel Wiki di Postgres e altra documentazione qui.

Metodi di accesso all'indice

PostgreSQL viene fornito con tipi di indice come B-Tree, hash, GIN e altro. È possibile scrivere il proprio tipo di indice simile a questo, come libreria condivisa C. Maggiori dettagli qui.

Metodi di accesso alle tabelle

Con il prossimo PostgreSQL 12, sarà possibile creare la propria struttura di archiviazione dati. Implementando l'interfaccia qui descritta, puoi memorizzare i dati tuple fisicamente su disco nel modo che preferisci.

Plugin di replica logica

In PostgreSQL, la replica logica viene implementata mediante una "decodifica" del contenuto del registro write-ahead (WAL) in un formato arbitrario (come testo SQL o json) e pubblicato agli abbonati su slot di replica. Questa decodifica viene eseguita tramite plug-in di output per la decodifica logica , che può essere implementata come libreria condivisa C come descritto qui. La libreria condivisa "test_decoding" è uno di questi plugin e puoi crearne uno tuo.

Gestione del linguaggio procedurale

Puoi anche aggiungere il supporto per il tuo linguaggio di programmazione preferito come Postgres PL creando un gestore, sempre come libreria condivisa C. Inizia qui per creare PL/Go o PL/Rust!

Estensioni

Le estensioni sono il modo di Postgres di gestione dei pacchetti. Supponiamo di avere una funzione C che fa qualcosa di utile e un paio di istruzioni SQL che rendono necessarie le istruzioni "CREA FUNZIONE" per configurarlo. Puoi raggrupparli come una "estensione" che Postgres può installare (e disinstallare) in un unico passaggio (chiamando "CREA ESTENSIONE"). Quando pubblichi una nuova versione, puoi anche includere i passaggi di aggiornamento anche nell'estensione.

Sebbene non siano di per sé la programmazione lato server, le estensioni sono il modo standard e molto efficiente per impacchettare e distribuire il codice lato server.

Ulteriori informazioni sulle estensioni sono disponibili qui e qui.