Il existe de nombreuses façons d'obtenir que le serveur Postgres exécute du code prédéfini. Vous trouverez ci-dessous une liste complète, avec des exemples, des façons dont vous pouvez laisser le serveur Postgres stocker une logique prédéfinie, que vous pourrez utiliser ultérieurement à partir de votre application.
Fonctions SQL
Postgres vous permet de créer des « fonctions définies par l'utilisateur », où le corps de la fonction peut être écrit dans un langage pris en charge. Les « fonctions SQL » sont des fonctions définies par l'utilisateur écrites en SQL standard, ce qui constitue le moyen le plus simple d'encapsuler des requêtes complexes et des séquences d'instructions SQL.
Voici quelques exemples :
-- 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;
Les fonctions SQL peuvent accepter et renvoyer des types de base, des types composites et des lignes. Ils prennent également en charge des nombres variables d'arguments, des valeurs par défaut pour les arguments et des arguments polymorphes. Ils peuvent même renvoyer plusieurs lignes, imitant un SELECT d'une table. Il n'est pas non plus nécessaire qu'ils retournent quoi que ce soit.
Cependant, le corps de la fonction ne peut contenir que des instructions SQL. Cela signifie qu'il n'y a pas d'instructions de contrôle de flux (if, while, …), de variables et autres.
La commande CREATE FUNCTION est utilisée pour créer la fonction. Comme d'habitude, vous pouvez les MODIFIER et les DÉPOSER.
C'est un excellent endroit pour commencer à creuser davantage :https://www.postgresql.org/docs/current/xfunc-sql.html
Fonctions C
Alors que les fonctions SQL sont les plus faciles à écrire et les moins puissantes, à l'autre extrémité du spectre, les fonctions peuvent être écrites en C et peuvent pratiquement tout faire. De telles fonctions doivent être codées en C et construites comme une bibliothèque partagée qui peut être chargée dynamiquement par Postgres.
Vous devez indiquer à Postgres où charger la bibliothèque partagée, le nom et la signature de la fonction :
CREATE FUNCTION sum(integer, integer) RETURNS integer
AS 'myfuncs', 'sum'
LANGUAGE C STRICT;
Cela dit que la bibliothèque partagée myfuncs.so
, présent dans un chemin de recherche prédéfini, contient des points d'entrée qui peuvent être appelés par Postgres, l'un des points d'entrée étant 'sum' qui peut être appelé en tant que fonction.
Le code réel en C serait trop long à inclure ici, mais vous pouvez tout lire à ce sujet dans la documentation. Combiné avec l'interface de programmation du serveur (SPI), il est possible d'effectuer presque toutes les opérations que vous pouvez effectuer de toute autre manière.
Par exemple, avec les fonctions C définies ici, vous pouvez effectuer des requêtes HTTP :
SELECT status, content_type FROM http_get('https://postgresql.org/');
Il est également possible d'écrire de telles bibliothèques partagées dans d'autres langages comme C++ ou Go, qui peuvent créer des bibliothèques partagées avec des liens "C".
Fonctions PL/pgSQL
Outre SQL et C, vous pouvez écrire des fonctions dans des langages procéduraux . Quatre de ces langages sont pris en charge par le noyau PostgreSQL - pgSQL, Python, Perl et Tcl. Le support de tout langage procédural lui-même provient d'une bibliothèque partagée C et fonctionne un peu comme mod_perl ou mod_python de l'ère Apache.
pgSQL est le langage canonique de type SQL le plus utilisé dans lequel les fonctions stockées pour PostgreSQL sont écrites. Il est disponible par défaut, grâce à son installation dans template1
.
PL/pgSQL est un langage à part entière avec des variables, des expressions et des instructions de contrôle ; et inclut des fonctionnalités telles que les curseurs pour travailler avec des données SQL en particulier. Il est largement documenté ici.
Voici un exemple :
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)
Autres langages procéduraux de base
Les autres langages procéduraux - Python, Perl, Tcl - permettent aux développeurs d'utiliser un langage avec lequel ils sont déjà à l'aise. Bien que la prise en charge de ces langages se trouve dans l'arborescence source de Postgres, les distributions n'installent généralement pas les binaires par défaut. Par exemple, dans Debian, vous devrez peut-être :
sudo apt install postgresql-plpython-11
pour installer le support PL/Python pour PostgreSQL 11.
Quel que soit le langage que vous utilisez pour écrire une fonction, l'appelant ne perçoit aucune différence dans son utilisation.
Python
L'extension PL/Python prend en charge les fonctions d'écriture en Python 2 et Python 3. Pour l'installer, faites :
CREATE EXTENSION plpythonu;
Voici une fonction écrite en PL/Python :
CREATE FUNCTION pymax (a integer, b integer)
RETURNS integer
AS $$
if a > b:
return a
return b
$$ LANGUAGE plpythonu;
L'environnement Python dans lequel le corps de la fonction s'exécute a un module appelé plpy
automatiquement importé dedans. Ce module contient des méthodes qui vous permettent de préparer et d'exécuter des requêtes, de gérer des transactions et de travailler avec des curseurs.
Plus d'informations peuvent être trouvées dans le chapitre 46 de la documentation Postgres.
Perl
Eh bien, oui, Perl. Les processus de développement, de test et de construction de Postgres utilisent Perextensivement, et il est également pris en charge en tant que langage procédural. Pour commencer à l'utiliser, assurez-vous que tous les packages binaires pertinents pour votre distribution sont installés (exemple "postgresql-plperl-nn" pour Debian) et installez l'extension "plperl".
Voici une fonction écrite en 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;
Documentation complète ici.
Tcl
Tcl est encore un autre PL pris en charge par le noyau Postgres. Voici un exemple :
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;
Pour plus d'informations, consultez la documentation ici.
Langages procéduraux non essentiels
Au-delà de ces langages, il existe des projets open source qui développent et maintiennent le support pour d'autres comme Java, Lua, R etc.
Il y a une liste ici :https://www.postgresql.org/docs/current/external-pl.html
Fonctions d'agrégation
Les fonctions d'agrégat fonctionnent sur un ensemble de valeurs et renvoient un seul résultat. PostgreSQL possède un ensemble de fonctions d'agrégat intégrées (voir la liste complète ici). Par exemple, pour obtenir l'écart type de population de toutes les valeurs d'une colonne, vous peut :
SELECT stddev_pop(grade) FROM students;
Vous pouvez définir vos propres fonctions d'agrégation qui se comportent de la même manière. Un agrégat défini par l'utilisateur est assemblé à partir de quelques fonctions autonomes individuelles qui fonctionnent sur l'état interne (par exemple, l'état interne d'un agrégat qui calcule la moyenne peut être des variables "somme" et "compte").
Voici un agrégat défini par l'utilisateur qui calcule la médiane d'un ensemble de valeurs :
-- 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='{}'
);
qui peut être invoqué comme :
SELECT median(grade) FROM students;
Pour plus d'informations, consultez la documentation sur les agrégats et l'instruction CREATE AGGREGATE.
Types définis par l'utilisateur
Les bibliothèques partagées écrites en C que nous avons vues précédemment peuvent non seulement définir des fonctions, mais également des types de données. Ces types définis par l'utilisateur peuvent être utilisés comme types de données pour les colonnes, tout comme les types intégrés. Vous pouvez définir des fonctions pour travailler avec les valeurs de vos types définis par l'utilisateur.
Il faut un peu de code pour définir un nouveau type. Consultez les documents ici qui vous guident dans la création d'un nouveau type pour représenter les nombres complexes. La source Postgres contient également un code de tutoriel pour cela.
Opérateurs
Les opérateurs facilitent l'utilisation des fonctions (par exemple, écrire 1 + 2
plutôt que sum(1, 2)
), et vous pouvez définir des opérateurs pour les types définis par l'utilisateur à l'aide de l'instruction CREATE OPERATOR.
Voici un exemple pour créer un +
opérateur qui correspond à la fonction complex_add
qui ajoute deux complex
nombres :
CREATE OPERATOR + (
leftarg = complex,
rightarg = complex,
function = complex_add,
commutator = +
);
Plus d'informations iciet ici.
Classes d'opérateurs et familles d'opérateurs
Les classes d'opérateurs permettent à votre type de données de fonctionner avec le B-Tree intégré et d'autres méthodes d'indexation. Par exemple, si vous souhaitez créer un index B-Tree sur une colonne de type "complexe", vous devrez indiquer à Postgres comment comparer deux valeurs de ce type pour déterminer si l'une est inférieure, égale ou supérieure à l'autre.
Il serait bon que les types complexes soient comparés à des entiers ou à des valeurs à virgule flottante, c'est là que les familles d'opérateurs entrent en jeu.
Vous pouvez lire tout sur les classes et les familles d'opérateurs ici.
Déclencheurs
Les déclencheurs sont un mécanisme puissant pour créer des effets secondaires pour les opérations normales, bien qu'ils puissent être dangereux s'ils sont surutilisés ou abusés. Essentiellement, les déclencheurs connectent les événements aux fonctions. La fonction référencée peut être invoquée :
- avant ou après l'insertion/mise à jour/suppression d'une ligne d'un tableau
- sur troncature d'un tableau
- au lieu d'insérer/mettre à jour/supprimer une ligne d'une vue
La fonction peut être invoquée pour chaque ligne affectée par une instruction, ou une fois par instruction. Et il y a encore plus de choses, comme la cascade de déclencheurs, qui sont toutes expliquées ici.
Les fonctions de déclenchement peuvent être écrites en C ou dans n'importe quelle fonction PL, mais pas en SQL. Voici un exemple pour insérer une ligne dans un audit tableau, pour chaque mise à jour apportée au prix d'un élément .
-- 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();
Lisez tout sur les déclencheurs ici, ainsi que la documentation CREATE TRIGGER.
Déclencheurs d'événements
Tant que déclenche répondre aux événements DML sur une seule table, déclencheurs d'événements peut répondre aux événements DDL sur une base de données particulière. Les événements incluent la création, la modification et la suppression d'une variété d'objets, tels que des tables, des index, des schémas, des vues, des fonctions, des types, des opérateurs, etc.
Voici un déclencheur d'événement qui empêche la suppression d'objets du schéma "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();
Plus d'informations peuvent être trouvéesici et dans la documentationCREATE EVENT TRIGGER.
Règles
PostgreSQL est livré avec une fonctionnalité qui vous permet de réécrire les requêtes avant qu'elles ne parviennent au planificateur de requêtes. L'opération est quelque peu similaire à la configuration de Nginx ou Apache pour réécrire une URL entrante avant de la traiter.
Voici deux exemples qui affectent les instructions INSERT sur une table et leur font faire autre chose :
-- 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);
Ce chapitre de la documentation contient plus d'informations sur les règles.
Procédures stockées
A partir de Postgres 11, il est possible de créer des procédures stockées Par rapport aux fonctions stockées, il n'y a qu'une seule chose supplémentaire que les procédures peuvent faire :le contrôle des transactions.
Voici un exemple :
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);
Voir ici et ici pour plus d'informations.
Autres objets exotiques
Enveloppeurs de données étrangères
Les wrappers de données étrangères (FDW) vous permettent de parler à d'autres sources de données, comme un autre serveur Postgres, MySQL, Oracle, Cassandra et plus encore. Toute la logique d'accès au serveur étranger est écrite en C, sous forme de bibliothèque partagée.
Il existe même un magasin en colonne appelé cstore_fdwbasé sur FDW.
Vous pouvez trouver une liste de l'implémentation de FDW dans le Wiki de Postgres et plus de documentation ici.
Méthodes d'accès à l'index
PostgreSQL est livré avec des types d'index tels que B-Tree, hash, GIN et plus encore. Il est possible d'écrire votre propre type d'index similaire à celui-ci, en tant que bibliothèque partagée C. Plus de détails ici.
Méthodes d'accès aux tables
Avec le prochain PostgreSQL 12, il sera possible de créer votre propre structure de stockage de données. En implémentant l'interface décrite ici, vous pouvez stocker les données du tuple physiquement sur le disque de la manière de votre choix.
Plug-ins de réplication logique
Dans PostgreSQL, la réplication logique est implémentée par un « décodage » du contenu du journal d'écriture anticipée (WAL) dans un format arbitraire (comme du texte SQL ou json) et publié aux abonnés via des emplacements de réplication. Ce décodage se fait via plugin de sortie de décodage logique , qui peut être implémentée en tant que bibliothèque partagée C, comme décrit ici. La bibliothèque partagée "test_decoding" est l'un de ces plugins, et vous pouvez créer la vôtre.
Gestionnaire de langage procédural
Vous pouvez également ajouter la prise en charge de votre langage de programmation préféré en tant que Postgres PL en créant un gestionnaire - encore une fois en tant que bibliothèque partagée C. Commencez ici pour créer PL/Go ou PL/Rust !
Extensions
Les extensions sont le mode de gestion des packages de Postgres. Disons que vous avez une fonction C qui fait quelque chose d'utile, et quelques instructions SQL qui font les instructions "CREATE FUNCTION" nécessaires pour la configurer. Vous pouvez les regrouper sous la forme d'une "extension" que Postgres peut installer (et désinstaller) en une seule étape (en appelant "CREATE EXTENSION"). Lorsque vous publiez une nouvelle version, vous pouvez également inclure des étapes de mise à niveau également dans l'extension.
Bien qu'il ne s'agisse pas de programmation côté serveur en soi, les extensions sont le moyen standard et très efficace de regrouper et de distribuer votre code côté serveur.
Vous trouverez plus d'informations sur les extensions iciet ici.