Cette extension est un linter complet pour PLPGSQL pour PostgreSQL. Il exploite uniquement l'analyseur / évaluateur postgresql interne, vous voyez donc exactement les erreurs se produiraient au moment de l'exécution. De plus, il analyse le SQL à l'intérieur de vos routines et trouve des erreurs qui ne sont généralement pas trouvées lors de la commande "Créer la procédure / fonction". Vous pouvez contrôler les niveaux de nombreux avertissements et indices. Enfin, vous pouvez ajouter des marqueurs de type pragama pour désactiver / sur de nombreux aspects vous permettant de masquer les messages que vous connaissez déjà, ou pour vous rappeler de revenir pour un nettoyage plus profond plus tard.
J'ai fondé ce projet, car je voulais publier le code que j'ai écrit pendant les deux années, lorsque j'ai essayé d'écrire une vérification améliorée pour PostgreSQL en amont. Il n'a pas été entièrement réussi - l'intégration en amont nécessite un refactorisation PLPGSQL plus grand. Mais le code est entièrement fonctionnel et peut être utilisé dans la production (et il est utilisé dans la production). J'ai donc créé cette extension pour être disponible pour tous les développeurs PLPGSQL.
Si si vous souhaitez rejoindre notre groupe pour aider le développement ultérieur de cette extension, inscrivez-vous dans ce groupe de piratage Google Postgresql Extension.
J'invite toutes les idées, correctifs, bugReports.
PostgreSQL PostgreSQL 12 - 17 est pris en charge.
Les instructions SQL à l'intérieur des fonctions PL / PGSQL sont vérifiées par le validateur pour les erreurs sémantiques. Ces erreurs peuvent être trouvées en appelant le plpgsql_check_function:
postgres=# CREATE EXTENSION plpgsql_check;
LOAD
postgres=# CREATE TABLE t1(a int, b int);
CREATE TABLE
postgres=#
CREATE OR REPLACE FUNCTION public.f1()
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE r record;
BEGIN
FOR r IN SELECT * FROM t1
LOOP
RAISE NOTICE '%', r.c; -- there is bug - table t1 missing "c" column
END LOOP;
END;
$function$;
CREATE FUNCTION
postgres=# select f1(); -- execution doesn't find a bug due to empty table t1
f1
────
(1 row)
postgres=# x
Expanded display is on.
postgres=# select * from plpgsql_check_function_tb('f1()');
─[ RECORD 1 ]───────────────────────────
functionid │ f1
lineno │ 6
statement │ RAISE
sqlstate │ 42703
message │ record "r" has no field "c"
detail │ [null]
hint │ [null]
level │ error
position │ 0
query │ [null]
postgres=# sf+ f1
CREATE OR REPLACE FUNCTION public.f1()
RETURNS void
LANGUAGE plpgsql
1 AS $function$
2 DECLARE r record;
3 BEGIN
4 FOR r IN SELECT * FROM t1
5 LOOP
6 RAISE NOTICE '%', r.c; -- there is bug - table t1 missing "c" column
7 END LOOP;
8 END;
9 $function$
Fonction plpgsql_check_function () a trois formats de sortie possibles: texte, json ou xml
select * from plpgsql_check_function('f1()', fatal_errors := false);
plpgsql_check_function
------------------------------------------------------------------------
error:42703:4:SQL statement:column "c" of relation "t1" does not exist
Query: update t1 set c = 30
-- ^
error:42P01:7:RAISE:missing FROM-clause entry for table "r"
Query: SELECT r.c
-- ^
error:42601:7:RAISE:too few parameters specified for RAISE
(7 rows)
postgres=# select * from plpgsql_check_function('fx()', format:='xml');
plpgsql_check_function
────────────────────────────────────────────────────────────────
<Function oid="16400"> ↵
<Issue> ↵
<Level>error</level> ↵
<Sqlstate>42P01</Sqlstate> ↵
<Message>relation "foo111" does not exist</Message> ↵
<Stmt lineno="3">RETURN</Stmt> ↵
<Query position="23">SELECT (select a from foo111)</Query>↵
</Issue> ↵
</Function>
(1 row)
Vous pouvez définir le niveau d'avertissements via les paramètres de la fonction:
funcoid oid - Nom de la fonction ou Signature de fonction - Les fonctions nécessitent une spécification de fonction. Toute fonction dans PostgreSQL peut être spécifiée par OID ou par nom ou par signature. Lorsque vous connaissez la signature de la fonction OID ou complète, vous pouvez utiliser un paramètre de type régprocedure comme 'fx()'::regprocedure ou 16799::regprocedure . L'alternative possible est d'utiliser un nom uniquement, lorsque le nom de la fonction est unique - comme 'fx' . Lorsque le nom n'est pas unique ou que la fonction n'existe pas, elle soulève une erreur. relid DEFAULT 0 - OID de relation attribué avec la fonction de déclenchement. Il est nécessaire de vérifier toute fonction de déclenchement. Vous envoyez le tableau en ce que le déclencheur fonctionne.
fatal_errors boolean DEFAULT true - Arrêtez-vous sur la première erreur (empêche les rapports d'erreur massifs)
other_warnings boolean DEFAULT true - Afficher les avertissements comme différents nombres d'attributs dans AssignMenet sur le côté gauche et droit, la variable chevauche le paramètre de la fonction, les variables inutilisées, le coulage indésirable, etc.
extra_warnings boolean DEFAULT true - Afficher les avertissements comme RETURN manquant, les variables ombragées, le code mort, le paramètre de la fonction (non utilisé), les variables non modifiées, les variables automobiles modifiées, etc.
performance_warnings boolean DEFAULT false - avertissements liés aux performances comme type déclaré type avec modificateur de type, moulage, jet implicite dans la clause où (peut être la raison pour laquelle un index n'est pas utilisé), etc.
security_warnings boolean DEFAULT false - Chèques liés à la sécurité comme la détection de vulnérabilité de l'injection SQL
compatibility_warnings boolean DEFAULT false - vérifications liées à la compatibilité comme le réglage explicite obsolète des noms de curseur internes dans les variables du refcursor ou du curseur.
anyelementtype regtype DEFAULT 'int' - un type réel à utiliser lors du test du type AnyElement
anyenumtype regtype DEFAULT '-' - un type réel à utiliser lors du test du type de tout
anyrangetype regtype DEFAULT 'int4range' - un type réel à utiliser lors du test du type AnyRange
anycompatibletype DEFAULT 'int' - un type réel à utiliser lors du test du type anycompatible
anycompatiblerangetype DEFAULT 'int4range' - un type de plage réel à utiliser lors du test du type de plage anycompatible
without_warnings DEFAULT false - Désactivez tous les avertissements (ignore tous les paramètres XXXX_WARNING, un remplacement rapide)
all_warnings DEFAULT false - Activer tous les avertissements (ignore d'autres paramètres xxx_warning, un positif rapide)
newtable DEFAULT NULL , oldtable DEFAULT NULL - Les noms des tables de transition nouvelles ou anciennes. Ces paramètres sont nécessaires lorsque les tables de transition sont utilisées dans les fonctions de déclenchement.
use_incomment_options DEFAULT true - Lorsqu'il est vrai, alors les options en comment sont actives
incomment_options_usage_warning DEFAULT false - Lorsqu'il est vrai, alors l'avertissement est soulevé lorsque l'option en comment est utilisée.
constant_tracing boolean DEFAULT true - Lorsqu'il est vrai, alors la variable qui contient un contenu constant, peut être utilisée comme constante (elle ne fonctionne que dans certains cas simples, et le contenu de la variable ne doit pas être ambigonous).
Lorsque vous souhaitez vérifier n'importe quel déclencheur, vous devez entrer une relation qui sera utilisée avec la fonction de déclenchement
CREATE TABLE bar(a int, b int);
postgres=# sf+ foo_trg
CREATE OR REPLACE FUNCTION public.foo_trg()
RETURNS trigger
LANGUAGE plpgsql
1 AS $function$
2 BEGIN
3 NEW.c := NEW.a + NEW.b;
4 RETURN NEW;
5 END;
6 $function$
Spécification de relation manquante
postgres=# select * from plpgsql_check_function('foo_trg()');
ERROR: missing trigger relation
HINT: Trigger relation oid must be valid
Vérification correcte du déclencheur (avec une relation spécifiée)
postgres=# select * from plpgsql_check_function('foo_trg()', 'bar');
plpgsql_check_function
--------------------------------------------------------
error:42703:3:assignment:record "new" has no field "c"
(1 row)
Pour les déclencheurs avec des tables transitifs, vous pouvez définir les paramètres oldtable et newtable :
create or replace function footab_trig_func()
returns trigger as $$
declare x int;
begin
if false then
-- should be ok;
select count(*) from newtab into x;
-- should fail;
select count(*) from newtab where d = 10 into x;
end if;
return null;
end;
$$ language plpgsql;
select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab');
PLPGSQL_CHECK permet un paramètre persistant écrit en commentaires. Ces options sont tirées du code source de la fonction avant la vérification. La syntaxe est:
@plpgsql_check_option: optioname [=] value [, optname [=] value ...]
Les paramètres des options de commentaire ont une priorité élevée, mais généralement il peut être désactivé par l'option use_incomment_options à false .
Exemple:
create or replace function fx(anyelement)
returns text as $$
begin
/*
* rewrite default polymorphic type to text
* @plpgsql_check_options: anyelementtype = text
*/
return $1;
end;
$$ language plpgsql;
Vous pouvez utiliser le PLPGSQL_CHECK_FUNCTION pour la vérification en masse des fonctions / procédures et la vérification en masse des déclencheurs. S'il vous plaît, testez les requêtes suivantes:
-- check all nontrigger plpgsql functions
SELECT p.oid, p.proname, plpgsql_check_function(p.oid)
FROM pg_catalog.pg_namespace n
JOIN pg_catalog.pg_proc p ON pronamespace = n.oid
JOIN pg_catalog.pg_language l ON p.prolang = l.oid
WHERE l.lanname = 'plpgsql' AND p.prorettype <> 2279;
ou
-- check all trigger plpgsql functions
SELECT p.proname, tgrelid::regclass, cf.*
FROM pg_proc p
JOIN pg_trigger t ON t.tgfoid = p.oid
JOIN pg_language l ON p.prolang = l.oid
JOIN pg_namespace n ON p.pronamespace = n.oid,
LATERAL plpgsql_check_function(p.oid, t.tgrelid) cf
WHERE n.nspname = 'public' and l.lanname = 'plpgsql';
ou
-- check all plpgsql functions (functions or trigger functions with defined triggers)
SELECT
(pcf).functionid::regprocedure, (pcf).lineno, (pcf).statement,
(pcf).sqlstate, (pcf).message, (pcf).detail, (pcf).hint, (pcf).level,
(pcf)."position", (pcf).query, (pcf).context
FROM
(
SELECT
plpgsql_check_function_tb(pg_proc.oid, COALESCE(pg_trigger.tgrelid, 0)) AS pcf
FROM pg_proc
LEFT JOIN pg_trigger
ON (pg_trigger.tgfoid = pg_proc.oid)
WHERE
prolang = (SELECT lang.oid FROM pg_language lang WHERE lang.lanname = 'plpgsql') AND
pronamespace <> (SELECT nsp.oid FROM pg_namespace nsp WHERE nsp.nspname = 'pg_catalog') AND
-- ignore unused triggers
(pg_proc.prorettype <> (SELECT typ.oid FROM pg_type typ WHERE typ.typname = 'trigger') OR
pg_trigger.tgfoid IS NOT NULL)
OFFSET 0
) ss
ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno;
Les fonctions peuvent être vérifiées lors de l'exécution - le module plpgsql_check doit être chargé (via postgresql.conf).
plpgsql_check.mode = [ disabled | by_function | fresh_start | every_start ]
plpgsql_check.fatal_errors = [ yes | no ]
plpgsql_check.show_nonperformance_warnings = false
plpgsql_check.show_performance_warnings = false
Le mode par défaut est by_function , cela signifie que la vérification améliorée est effectuée uniquement en mode actif - en appelant le PLPGSQL_CHECK_FUNCTION . fresh_start signifie démarrage à froid (d'abord la fonction est appelée).
Vous pouvez activer le mode passif par
load 'plpgsql'; -- 1.1 and higher doesn't need it
load 'plpgsql_check';
set plpgsql_check.mode = 'every_start'; -- This scans all code before it is executed
SELECT fx(10); -- run functions - function is checked before runtime starts it
Les variables de PostgreSQL Cursor et Refcursor sont des variables de chaîne améliorées qui contient le nom unique du portail connexe (structure interne de Postgres qui est utilisée pour l'implémentation de Cursor). Jusqu'à postgresql 16, le portail avait le même nom comme le nom de la variable de curseur. PostgreSQL 16 et un changement supérieur ce mécanisme et par défaut, le portail connexe sera nommé par un nom unique. Il résout certains problèmes avec les curseurs dans les blocs imbriqués ou lorsque le curseur est utilisé dans une fonction récursive appelée.
Avec le changement mentionné, la variable du refcursor devrait prendre la valeur d'une autre variable de refCursor ou d'une variable de curseur (lorsque le curseur est ouvert).
-- obsolete pattern
DECLARE
cur CURSOR FOR SELECT 1;
rcur refcursor;
BEGIN
rcur := 'cur';
OPEN cur;
...
-- new pattern
DECLARE
cur CURSOR FOR SELECT 1;
rcur refcursor;
BEGIN
OPEN cur;
rcur := cur;
...
Lorsque l'indicateur compatibility_warnings est actif, alors plpgsql_check Essayez d'identifier une attribution de poisson à la variable de Refcursor ou le retour des valeurs de Refcursor:
CREATE OR REPLACE FUNCTION public.foo()
RETURNS refcursor
AS $$
declare
c cursor for select 1;
r refcursor;
begin
open c;
r := 'c';
return r;
end;
$$ LANGUAGE plpgsql;
select * from plpgsql_check_function('foo', extra_warnings =>false, compatibility_warnings => true);
┌───────────────────────────────────────────────────────────────────────────────────┐
│ plpgsql_check_function │
╞═══════════════════════════════════════════════════════════════════════════════════╡
│ compatibility:00000:6:assignment:obsolete setting of refcursor or cursor variable │
│ Detail: Internal name of cursor should not be specified by users. │
│ Context: at assignment to variable "r" declared on line 3 │
└───────────────────────────────────────────────────────────────────────────────────┘
(3 rows)
PLPGSQL_CHECK devrait trouver presque toutes les erreurs sur un code vraiment statique. Lorsque les développeurs utilisent les fonctionnalités dynamiques de PLPGSQL comme Dynamic SQL ou le type de données d'enregistrement, des faux positifs sont possibles. Ceux-ci doivent être rares - dans un code bien écrit - puis la fonction affectée doit être redessinée ou PLPGSQL_CHECK doit être désactivé pour cette fonction.
CREATE OR REPLACE FUNCTION f1()
RETURNS void AS $$
DECLARE r record;
BEGIN
FOR r IN EXECUTE 'SELECT * FROM t1'
LOOP
RAISE NOTICE '%', r.c;
END LOOP;
END;
$$ LANGUAGE plpgsql SET plpgsql.enable_check TO false;
Une utilisation de PLPGSQL_CHECK ajoute une petite surcharge (lorsque le mode passif est activé) et vous devez utiliser ce paramètre uniquement dans les environnements de développement ou de préproduction.
Ce module ne vérifie pas les requêtes assemblées lors de l'exécution. Il n'est pas possible d'identifier les résultats des requêtes dynamiques - donc PLPGSQL_CHECK ne peut pas définir le type correct pour enregistrer les variables et ne peut pas vérifier un SQL et des expressions dépendants.
Lorsque la variable de type d'enregistrement n'est pas connue, vous pouvez l'affecter explicitement avec type Pragma:
DECLARE r record;
BEGIN
EXECUTE format('SELECT * FROM %I', _tablename) INTO r;
PERFORM plpgsql_check_pragma('type: r (id int, processed bool)');
IF NOT r.processed THEN
...
PLPGSQL_CHECK ne peut pas être utilisé pour détecter la structure des curseurs référencés. Une référence sur le curseur dans PLPGSQL est implémentée comme le nom du curseur global. En cas de temps, le nom n'est pas connu (pas en toutes possibilités) et le curseur global n'existe pas. C'est un problème important pour toute analyse statique. PLPGSQL ne peut pas savoir comment définir le type correct pour les variables d'enregistrement et ne peut pas vérifier les instructions et expressions SQL dépendantes. Une solution est la même pour SQL dynamique. N'utilisez pas la variable d'enregistrement comme cible lorsque vous utilisez le type de refCursor ou désactivez PLPGSQL_CHECK pour ces fonctions.
CREATE OR REPLACE FUNCTION foo(refcur_var refcursor)
RETURNS void AS $$
DECLARE
rec_var record;
BEGIN
FETCH refcur_var INTO rec_var; -- this is STOP for plpgsql_check
RAISE NOTICE '%', rec_var; -- record rec_var is not assigned yet error
Dans ce cas, un type d'enregistrement ne doit pas être utilisé (utilisez plutôt RowType connu):
CREATE OR REPLACE FUNCTION foo(refcur_var refcursor)
RETURNS void AS $$
DECLARE
rec_var some_rowtype;
BEGIN
FETCH refcur_var INTO rec_var;
RAISE NOTICE '%', rec_var;
PLPGSQL_CHECK ne peut pas vérifier les requêtes sur les tables temporaires qui sont créées dans l'exécution de la fonction de PLPGSQL. Pour ce cas d'utilisation, il est nécessaire de créer une fausse table temporaire ou de désactiver PLPGSQL_CHECK pour cette fonction.
En réalité, les tables temporaires sont stockées dans le schéma propre (par utilisateur) avec une priorité plus élevée que les tables persistantes. Vous pouvez donc le faire (avec des astuces suivantes en toute sécurité):
CREATE OR REPLACE FUNCTION public.disable_dml()
RETURNS trigger
LANGUAGE plpgsql AS $function$
BEGIN
RAISE EXCEPTION SQLSTATE '42P01'
USING message = format('this instance of %I table doesn''t allow any DML operation', TG_TABLE_NAME),
hint = format('you should use "CREATE TEMP TABLE %1$I(LIKE %1$I INCLUDING ALL);" statement',
TG_TABLE_NAME);
RETURN NULL;
END;
$function$;
CREATE TABLE foo(a int, b int); -- doesn't hold data, ever
CREATE TRIGGER foo_disable_dml
BEFORE INSERT OR UPDATE OR DELETE ON foo
EXECUTE PROCEDURE disable_dml();
postgres=# INSERT INTO foo VALUES(10,20);
ERROR: this instance of foo table doesn't allow any DML operation
HINT: you should to run "CREATE TEMP TABLE foo(LIKE foo INCLUDING ALL);" statement
postgres=#
CREATE TABLE
postgres=# INSERT INTO foo VALUES(10,20);
INSERT 0 1
Cette astuce émule partiellement les tables temporaires globales et permet une validation statistique. Une autre possibilité consiste à utiliser un [modèle de wrapper de données étrangères] (https://github.com/okbob/template_fdw)
Vous pouvez utiliser table Pragma et créer une table éphémère:
BEGIN
CREATE TEMP TABLE xxx(a int);
PERFORM plpgsql_check_pragma('table: xxx(a int)');
INSERT INTO xxx VALUES(10);
PERFORM plpgsql_check_pragma('table: [pg_temp].zzz(like schemaname.table1 including all)');
...
Une fonction plpgsql_show_dependency_tb affichera toutes les fonctions, opérateurs et relations utilisés à l'intérieur de la fonction traitée:
postgres=# select * from plpgsql_show_dependency_tb('testfunc(int,float)');
┌──────────┬───────┬────────┬─────────┬────────────────────────────┐
│ type │ oid │ schema │ name │ params │
╞══════════╪═══════╪════════╪═════════╪════════════════════════════╡
│ FUNCTION │ 36008 │ public │ myfunc1 │ (integer,double precision) │
│ FUNCTION │ 35999 │ public │ myfunc2 │ (integer,double precision) │
│ OPERATOR │ 36007 │ public │ ** │ (integer,integer) │
│ RELATION │ 36005 │ public │ myview │ │
│ RELATION │ 36002 │ public │ mytable │ │
└──────────┴───────┴────────┴─────────┴────────────────────────────┘
(4 rows)
Les arguments facultatifs de plpgsql_show_dependency_tb sont relid , anyelementtype , enumtype , anyrangetype , anycompatibletype et anycompatiblerangetype .
Le PLPGSQL_CHECK contient un profileur simple des fonctions et procédures PLPGSQL. Il peut fonctionner avec / sans accès à la mémoire partagée. Cela dépend de la configuration shared_preload_libraries . Lorsque PLPGSQL_CHECK est initialisé par shared_preload_libraries , il peut allouer la mémoire partagée et les profils de la fonction y sont stockés. Lorsque PLPGSQL_CHECK ne peut pas allouer la mémoire partagée, le profil est stocké dans la mémoire de session.
En raison des dépendances, shared_preload_libraries doit contenir d'abord plpgsql
postgres=# show shared_preload_libraries ;
┌──────────────────────────┐
│ shared_preload_libraries │
╞══════════════════════════╡
│ plpgsql,plpgsql_check │
└──────────────────────────┘
(1 row)
Le profileur est actif lorsque GUC plpgsql_check.profiler est allumé. Le profileur ne nécessite pas de mémoire partagée, mais s'il n'y a pas assez de mémoire partagée, le profileur est limité uniquement à la session active. Le profileur peut être activé en appelant la fonction plpgsql_check_profiler(true) et désactivé en appelant la même fonction avec false argument (ou avec des littéraux on , off ).
Le PLPGSQL_CHECK doit être initialisé avant que toute fonction PLPGSQL ne soit exécutée. Seule l'initialisation précoce garantit un travail correct du profileur et du traceur. Lorsque vous n'utilisez pas shared_preloaded_libraries , vous pouvez utiliser load 'plpgsql_check' à la place.
Lorsque PLPGSQL_CHECK est initialisé par shared_preload_libraries , un autre GUC est disponible pour configurer la quantité de mémoire partagée utilisée par le profileur: plpgsql_check.profiler_max_shared_chunks . Cela définit le nombre maximum de chunks qui peuvent être stockés dans la mémoire partagée. Pour chaque fonction (ou procédure) PLPGSQL, le contenu entier est divisé en morceaux de 30 instructions. Si nécessaire, plusieurs morceaux peuvent être utilisés pour stocker l'intégralité du contenu d'une seule fonction. Un seul morceau est de 1704 octets. La valeur par défaut pour ce GUC est de 15000, ce qui devrait être suffisant pour les grands projets contenant des centaines de milliers de déclarations dans PLPGSQL, et consommera environ 24 Mo de mémoire. Si votre projet ne nécessite pas autant de morceaux de morceaux, vous pouvez définir ce paramètre sur un nombre plus petit afin de diminuer l'utilisation de la mémoire. La valeur minimale est de 50 (ce qui devrait consommer environ 83 Ko de mémoire), et la valeur maximale est de 100000 (ce qui devrait consommer environ 163 Mo de mémoire). La modification de ce paramètre nécessite un redémarrage postgresql.
Le profileur récupérera également l'identifiant de requête pour chaque instruction qui contient une expression ou une instruction optimisable. Notez que cela nécessite PG_STAT_STATSTS, ou une autre extension tierce similaire), à installer. Il y a certaines limites à la récupération de l'identifiant de requête:
ATTENTION: Une mise à jour des profils partagés peut réduire les performances sur les serveurs sous une charge plus élevée.
Le profil peut être affiché par fonction plpgsql_profiler_function_tb :
postgres=# select lineno, avg_time, source from plpgsql_profiler_function_tb('fx(int)');
┌────────┬──────────┬───────────────────────────────────────────────────────────────────┐
│ lineno │ avg_time │ source │
╞════════╪══════════╪═══════════════════════════════════════════════════════════════════╡
│ 1 │ │ │
│ 2 │ │ declare result int = 0; │
│ 3 │ 0.075 │ begin │
│ 4 │ 0.202 │ for i in 1..$1 loop │
│ 5 │ 0.005 │ select result + i into result; select result + i into result; │
│ 6 │ │ end loop; │
│ 7 │ 0 │ return result; │
│ 8 │ │ end; │
└────────┴──────────┴───────────────────────────────────────────────────────────────────┘
(9 rows)
Les temps dans le résultat sont en misécondes.
Le profil par instructions (non par ligne) peut être affiché par fonction plpgsql_profiler_function_statements_tb:
CREATE OR REPLACE FUNCTION public.fx1(a integer)
RETURNS integer
LANGUAGE plpgsql
1 AS $function$
2 begin
3 if a > 10 then
4 raise notice 'ahoj';
5 return -1;
6 else
7 raise notice 'nazdar';
8 return 1;
9 end if;
10 end;
11 $function$
postgres=# select stmtid, parent_stmtid, parent_note, lineno, exec_stmts, stmtname
from plpgsql_profiler_function_statements_tb('fx1');
┌────────┬───────────────┬─────────────┬────────┬────────────┬─────────────────┐
│ stmtid │ parent_stmtid │ parent_note │ lineno │ exec_stmts │ stmtname │
╞════════╪═══════════════╪═════════════╪════════╪════════════╪═════════════════╡
│ 0 │ ∅ │ ∅ │ 2 │ 0 │ statement block │
│ 1 │ 0 │ body │ 3 │ 0 │ IF │
│ 2 │ 1 │ then body │ 4 │ 0 │ RAISE │
│ 3 │ 1 │ then body │ 5 │ 0 │ RETURN │
│ 4 │ 1 │ else body │ 7 │ 0 │ RAISE │
│ 5 │ 1 │ else body │ 8 │ 0 │ RETURN │
└────────┴───────────────┴─────────────┴────────┴────────────┴─────────────────┘
(6 rows)
Tous les profils stockés peuvent être affichés en appelant la fonction plpgsql_profiler_functions_all :
postgres=# select * from plpgsql_profiler_functions_all();
┌───────────────────────┬────────────┬────────────┬──────────┬─────────────┬──────────┬──────────┐
│ funcoid │ exec_count │ total_time │ avg_time │ stddev_time │ min_time │ max_time │
╞═══════════════════════╪════════════╪════════════╪══════════╪═════════════╪══════════╪══════════╡
│ fxx(double precision) │ 1 │ 0.01 │ 0.01 │ 0.00 │ 0.01 │ 0.01 │
└───────────────────────┴────────────┴────────────┴──────────┴─────────────┴──────────┴──────────┘
(1 row)
Il existe deux fonctions pour nettoyer les profils stockés: plpgsql_profiler_reset_all() et plpgsql_profiler_reset(regprocedure) .
PLPGSQL_CHECK fournit deux fonctions:
plpgsql_coverage_statements(name)plpgsql_coverage_branches(name)Il y a un autre très bon profileur PLPGSQL - https://github.com/glynastill/plprofiler
Mon extension est conçue pour être simple pour une utilisation et pratique. Rien de plus ou moins.
Le plprofiler est plus complexe. Il construit des graphiques d'appels et à partir de ce graphique, il peut créer un graphique de flamme des temps d'exécution.
Les deux extensions peuvent être utilisées avec la fonctionnalité de suivi de PostgreSQL intégrée.
set track_functions to 'pl';
...
select * from pg_stat_user_functions;
PLPGSQL_CHECK offre une possibilité de traçage - dans ce mode, vous pouvez voir les avis sur les fonctions de démarrage ou de fin (interdépendance de démarrage et par défaut) et de démarrage ou de fin (verbosité verbeux). Pour la verbosité par défaut et verbeux, le contenu des arguments de fonction s'affiche. Le contenu des variables connexes est affiché lorsque la verbosité est verbeuse.
postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE: #0 ->> start of inline_code_block (Oid=0)
NOTICE: #2 ->> start of function fx(integer,integer,date,text) (Oid=16405)
NOTICE: #2 call by inline_code_block line 1 at PERFORM
NOTICE: #2 "a" => '10', "b" => null, "c" => '2020-08-03', "d" => 'stěhule'
NOTICE: #4 ->> start of function fx(integer) (Oid=16404)
NOTICE: #4 call by fx(integer,integer,date,text) line 1 at PERFORM
NOTICE: #4 "a" => '10'
NOTICE: #4 <<- end of function fx (elapsed time=0.098 ms)
NOTICE: #2 <<- end of function fx (elapsed time=0.399 ms)
NOTICE: #0 <<- end of block (elapsed time=0.754 ms)
Le numéro après # est un compteur de trame d'exécution (ce nombre est lié à la profondeur de la pile de contexte d'erreur). Il permet de coupler le démarrage et la fin de la fonction.
Le traçage est activé en définissant plpgsql_check.tracer sur on . ATTENTION - Permettre à ce comportement a un impact négatif significatif sur les performances (contrairement au profileur). Vous pouvez définir un niveau pour la sortie utilisée par Tracer plpgsql_check.tracer_errlevel (Par défaut est notice ). Le contenu de sortie est limité par la longueur spécifiée par plpgsql_check.tracer_variable_max_length Variable de configuration. Le traceur peut être activé en appelant la fonction plpgsql_check_tracer(true) et désactivé en appelant la même fonction avec false argument (ou avec des littéraux on , off ).
Premièrement, l'utilisation de Tracer doit être explicitement activée par SuperUser en définissant set plpgsql_check.enable_tracer to on; ou plpgsql_check.enable_tracer to on dans postgresql.conf . Il s'agit d'une sauvegarde de sécurité. Le traceur affiche le contenu des variables de PLPGSQL, puis certaines informations sensibles à la sécurité peuvent être affichées à un utilisateur improvisé (lorsqu'il exécute la fonction de sécurité de sécurité). Deuxièmement, l'extension plpgsql_check doit être chargée. Il peut être fait par exécution de certaines fonctions plpgsql_check ou explicitement par load 'plpgsql_check'; . Vous pouvez utiliser l'option de configuration shared_preload_libraries , local_preload_libraries ou session_preload_libraries .
En mode verbeux laconique, la sortie est réduite:
postgres=# set plpgsql_check.tracer_verbosity TO terse;
SET
postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE: #0 start of inline code block (oid=0)
NOTICE: #2 start of fx (oid=16405)
NOTICE: #4 start of fx (oid=16404)
NOTICE: #4 end of fx
NOTICE: #2 end of fx
NOTICE: #0 end of inline code block
En mode verbeux, la sortie est étendue sur les détails de l'instruction:
postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE: #0 ->> start of block inline_code_block (oid=0)
NOTICE: #0.1 1 --> start of PERFORM
NOTICE: #2 ->> start of function fx(integer,integer,date,text) (oid=16405)
NOTICE: #2 call by inline_code_block line 1 at PERFORM
NOTICE: #2 "a" => '10', "b" => null, "c" => '2020-08-04', "d" => 'stěhule'
NOTICE: #2.1 1 --> start of PERFORM
NOTICE: #2.1 "a" => '10'
NOTICE: #4 ->> start of function fx(integer) (oid=16404)
NOTICE: #4 call by fx(integer,integer,date,text) line 1 at PERFORM
NOTICE: #4 "a" => '10'
NOTICE: #4.1 6 --> start of assignment
NOTICE: #4.1 "a" => '10', "b" => '20'
NOTICE: #4.1 <-- end of assignment (elapsed time=0.076 ms)
NOTICE: #4.1 "res" => '130'
NOTICE: #4.2 7 --> start of RETURN
NOTICE: #4.2 "res" => '130'
NOTICE: #4.2 <-- end of RETURN (elapsed time=0.054 ms)
NOTICE: #4 <<- end of function fx (elapsed time=0.373 ms)
NOTICE: #2.1 <-- end of PERFORM (elapsed time=0.589 ms)
NOTICE: #2 <<- end of function fx (elapsed time=0.727 ms)
NOTICE: #0.1 <-- end of PERFORM (elapsed time=1.147 ms)
NOTICE: #0 <<- end of block (elapsed time=1.286 ms)
Une caractéristique spéciale de Tracer est le traçage de l'instruction ASSERT lorsque plpgsql_check.trace_assert est on . Lorsque plpgsql_check.trace_assert_verbosity est DEFAULT , toutes les variables de la fonction ou de la procédure sont affichées lorsque l'expression de l'affirmation est fausse. Lorsque cette configuration est VERBOSE , toutes les variables de toutes les trames PLPGSQL sont affichées. Ce comportement est indépendant sur la valeur plpgsql.check_asserts . Il peut être utilisé, bien que les assertions soient désactivées dans PLPGSQL d'exécution.
postgres=# set plpgsql_check.tracer to off;
postgres=# set plpgsql_check.trace_assert_verbosity TO verbose;
postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE: #4 PLpgSQL assert expression (false) on line 12 of fx(integer) is false
NOTICE: "a" => '10', "res" => null, "b" => '20'
NOTICE: #2 PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM
NOTICE: "a" => '10', "b" => null, "c" => '2020-08-05', "d" => 'stěhule'
NOTICE: #0 PL/pgSQL function inline_code_block line 1 at PERFORM
ERROR: assertion failed
CONTEXT: PL/pgSQL function fx(integer) line 12 at ASSERT
SQL statement "SELECT fx(a)"
PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM
SQL statement "SELECT fx(10,null, 'now', e'stěhule')"
PL/pgSQL function inline_code_block line 1 at PERFORM
postgres=# set plpgsql.check_asserts to off;
SET
postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$;
NOTICE: #4 PLpgSQL assert expression (false) on line 12 of fx(integer) is false
NOTICE: "a" => '10', "res" => null, "b" => '20'
NOTICE: #2 PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM
NOTICE: "a" => '10', "b" => null, "c" => '2020-08-05', "d" => 'stěhule'
NOTICE: #0 PL/pgSQL function inline_code_block line 1 at PERFORM
DO
Le traceur peut montrer l'utilisation de l'ID de tampon de sous-transaction ( nxids ). Le numéro tnl affiché est le numéro de niveau de nidification de la transaction (pour PLPGSQL, il dépend du profond des blocs avec les gestionnaires de l'exception).
Les curseurs de PLPGSQL ne sont que des noms de curseurs SQL. Le cycle de vie des curseurs SQL n'est pas joint à la portée de la variable de curseur de PLPGSQL associée. Les curseurs SQL sont dans les cursements de soi à la fin de la transaction, mais pour une transaction longue et des curseurs trop ouverts, il peut être trop tard. Il est préférable de fermer explicitement le curseur lorsque le curseur n'est pas nécessaire (par déclaration étroite). Sans cela, des problèmes de mémoire importants sont possibles.
Lorsque Open Statement essaie d'utiliser un curseur qui n'est pas encore fermé, l'avertissement est augmenté. Cette fonctionnalité peut être désactivée en définissant plpgsql_check.cursors_leaks to off . Ce chèque n'est pas actif, lorsque la routine est appelée récusation
Les curseurs non clos peuvent être vérifiés immédiatement lorsque la fonction est terminée. Cette vérification est désactivée par défaut et doit être activée par plpgsql_check.strict_cursors_leaks to on .
Tout curseur non clos est signalé une fois.
Si vous utilisez plugin_debugger (PLPGSQL Debugger) avec plpgsql_check , alors plpgsql_check doit être initialisé après plugin_debugger (car plugin_debugger ne prend pas en charge le partage de l'API Debug de PL / PGSQL). Par exemple ( postgresql.conf ):
shared_preload_libraries = 'plugin_debugger,plpgsql,plpgsql_check'
Tracer imprime le contenu des variables ou des arguments de fonction. Pour la fonction de sécurité de sécurité, ce contenu peut contenir des données sensibles à la sécurité. C'est la raison pour laquelle Tracer est désactivé par défaut et ne devrait être activé qu'avec des droits de super utilisateur plpgsql_check.enable_tracer .
Vous pouvez configurer le comportement PLPGSQL_CHECK dans une fonction vérifiée avec une fonction "pragma". Il s'agit d'une analogie de la fonction PL / SQL ou ADA de la fonctionnalité de Pragma. PLPGSQL ne prend pas en charge Pragma, mais PLPGSQL_CHECK détecte la fonction nommée plpgsql_check_pragma et prend des options à partir des paramètres de cette fonction. Ces options PLPGSQL_CHECK sont valables à la fin de ce groupe d'instructions.
CREATE OR REPLACE FUNCTION test()
RETURNS void AS $$
BEGIN
...
-- for following statements disable check
PERFORM plpgsql_check_pragma('disable:check');
...
-- enable check again
PERFORM plpgsql_check_pragma('enable:check');
...
END;
$$ LANGUAGE plpgsql;
La fonction plpgsql_check_pragma est une fonction immuable qui en renvoie une. Il est défini par l'extension plpgsql_check . Vous pouvez déclarer une fonction plpgsql_check_pragma alternative comme:
CREATE OR REPLACE FUNCTION plpgsql_check_pragma(VARIADIC args[])
RETURNS int AS $$
SELECT 1
$$ LANGUAGE sql IMMUTABLE;
Utilisation de la fonction Pragma dans la partie de déclaration des options de sets de blocs supérieurs au niveau de la fonction.
CREATE OR REPLACE FUNCTION test()
RETURNS void AS $$
DECLARE
aux int := plpgsql_check_pragma('disable:extra_warnings');
...
La syntaxe plus courte pour Pragma est également prise en charge:
CREATE OR REPLACE FUNCTION test()
RETURNS void AS $$
DECLARE r record;
BEGIN
PERFORM 'PRAGMA:TYPE:r (a int, b int)';
PERFORM 'PRAGMA:TABLE: x (like pg_class)';
...
echo:str - Imprimer la chaîne (pour les tests). À l'intérieur de la chaîne, il peut y avoir des "variables": @@ id, @@ nom, @@ signature
status:check , status:tracer , status:other_warnings , status:performance_warnings , status:extra_warnings , status:security_warnings Ceci étend la valeur actuelle (par exemple autre_warnings activé)
enable:check , enable:tracer , enable:other_warnings , enable:performance_warnings , enable:extra_warnings , enable:security_warnings
disable:check , disable:tracer , disable:other_warnings , disable:performance_warnings , disable:extra_warnings , disable:security_warnings Cela peut être utilisé pour désactiver l'indice de retour d'une fonction anyelement. Il suffit de mettre le pragma avant l'énoncé de retour.
type:varname typename ou type:varname (fieldname type, ...) - Définir le type sur la variable du type d'enregistrement
table: name (column_name type, ...) ou table: name (like tablename) - Créer une table temporaire éphémère (si vous souhaitez spécifier le schéma, alors seul le schéma pg_temp est autorisé.
sequence: name - Créer une séquence temporaire éphémère
assert-schema: varname - Affirmation de temps de contrôle - Assurez-vous que le schéma spécifié par la variable est valide
assert-table: [ varname_schema, ] , varname - assurez-vous que le nom de la table spécifié par les variables (par traçage constant) est valide
assert-column: [varname_schema, ], varname_table , varname - assurez-vous que la colonne spectifiée par les variables est valide
Les pragmas enable:tracer et disable:tracer est actif pour les postgres 12 et plus
PLPGSQL_CHECK ne prend pas en charge la mise à jour (de PLPGSQL_CHECK). Vous devez le supprimer avant d'installer une nouvelle version de cette extension.
Vous avez besoin d'un environnement de développement pour les extensions postgresql:
make clean
make install
résultat:
[pavel@localhost plpgsql_check]$ make USE_PGXS=1 clean
rm -f plpgsql_check.so libplpgsql_check.a libplpgsql_check.pc
rm -f plpgsql_check.o
rm -rf results/ regression.diffs regression.out tmp_check/ log/
[pavel@localhost plpgsql_check]$ make USE_PGXS=1 all
clang -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fpic -I/usr/local/pgsql/lib/pgxs/src/makefiles/../../src/pl/plpgsql/src -I. -I./ -I/usr/local/pgsql/include/server -I/usr/local/pgsql/include/internal -D_GNU_SOURCE -c -o plpgsql_check.o plpgsql_check.c
clang -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fpic -I/usr/local/pgsql/lib/pgxs/src/makefiles/../../src/pl/plpgsql/src -shared -o plpgsql_check.so plpgsql_check.o -L/usr/local/pgsql/lib -Wl,--as-needed -Wl,-rpath,'/usr/local/pgsql/lib',--enable-new-dtags
[pavel@localhost plpgsql_check]$ su root
Password: *******
[root@localhost plpgsql_check]# make USE_PGXS=1 install
/usr/bin/mkdir -p '/usr/local/pgsql/lib'
/usr/bin/mkdir -p '/usr/local/pgsql/share/extension'
/usr/bin/mkdir -p '/usr/local/pgsql/share/extension'
/usr/bin/install -c -m 755 plpgsql_check.so '/usr/local/pgsql/lib/plpgsql_check.so'
/usr/bin/install -c -m 644 plpgsql_check.control '/usr/local/pgsql/share/extension/'
/usr/bin/install -c -m 644 plpgsql_check--0.9.sql '/usr/local/pgsql/share/extension/'
[root@localhost plpgsql_check]# exit
[pavel@localhost plpgsql_check]$ make USE_PGXS=1 installcheck
/usr/local/pgsql/lib/pgxs/src/makefiles/../../src/test/regress/pg_regress --inputdir=./ --psqldir='/usr/local/pgsql/bin' --dbname=pl_regression --load-language=plpgsql --dbname=contrib_regression plpgsql_check_passive plpgsql_check_active plpgsql_check_active-9.5
(using postmaster on Unix socket, default port)
============== dropping database "contrib_regression" ==============
DROP DATABASE
============== creating database "contrib_regression" ==============
CREATE DATABASE
ALTER DATABASE
============== installing plpgsql ==============
CREATE LANGUAGE
============== running regression test queries ==============
test plpgsql_check_passive ... ok
test plpgsql_check_active ... ok
test plpgsql_check_active-9.5 ... ok
=====================
All 3 tests passed.
=====================
Parfois, une compilation réussie peut nécessiter un package LiBicu-DEV (PostgreSQL 10 et plus - lorsque PG a été compilé avec le support en USI)
sudo apt install libicu-dev
Vous pouvez consulter les bibliothèques DLL précompilées http://okbob.blogspot.cz/2015/02/plpgsqlcheck-is-available-for-microsoft.html, http://okbob.blogspot.com/2023/10/compiled-dll-flpgsqlllcheck-202-and.html
ou compiler par soi:
plpgsql_check.dll à PostgreSQL14libplpgsql_check.control et plpgsql_check--2.1.sql à PostgreSQL14shareextension meson setup buildcd buildninjasudo ninja installLa compilation contre Postgresql 10 nécessite LiBicu!
Copyright (c) Pavel Stehule ([email protected])
L'autorisation est accordée gratuitement à toute personne qui obtient une copie de ce logiciel et des fichiers de documentation associés (le "logiciel"), pour traiter le logiciel sans restriction, y compris sans limiter les droits d'utilisation, de copie, de modification, de fusion, de publication, de distribution, de sublince et / ou de vendre des copies des conditions suivantes.
L'avis de droit d'auteur ci-dessus et le présent avis d'autorisation sont inclus dans toutes les copies ou des parties substantielles du logiciel.
Le logiciel est fourni "tel quel", sans garantie d'aucune sorte, express ou implicite, y compris, mais sans s'y limiter, les garanties de qualité marchande, d'adéquation à un usage particulier et de non-contrefaçon. En aucun cas, les auteurs ou les détenteurs de droits d'auteur ne seront pas responsables de toute réclamation, dommage ou autre responsabilité, que ce soit dans une action de contrat, de délit ou autre, découlant de, hors du logiciel ou de l'utilisation ou d'autres relations dans le logiciel.
Si vous l'aimez, envoyez une carte postale pour s'adresser
Pavel Stehule
Skalice 12
256 01 Benesov u Prahy
Czech Republic
J'invite toutes les questions, commentaires, rapports de bogues, correctifs sur l'adresse du courrier [email protected]