🔝 Retour au Sommaire
PL/Perl est un langage procédural pour PostgreSQL qui permet d'écrire des fonctions et des procédures stockées en Perl. Perl, connu pour ses capacités exceptionnelles en manipulation de texte et d'expressions régulières, apporte sa puissance au sein de PostgreSQL.
Perl (Practical Extraction and Report Language) est un langage de programmation créé en 1987 par Larry Wall. Il est particulièrement réputé pour :
- Traitement de texte : Parsing, recherche, remplacement
- Expressions régulières : Les plus puissantes parmi les langages mainstream
- Flexibilité : "There's more than one way to do it" (TMTOWTDI)
- CPAN : Comprehensive Perl Archive Network - des milliers de modules
- Manipulation de texte : Parsing, extraction, transformation de chaînes complexes
- Expressions régulières avancées : Recherche de patterns sophistiqués
- Écosystème CPAN : Accès à des milliers de modules Perl
- Traitement de logs : Analyse et extraction d'informations
- Formats de données : CSV, XML, formats propriétaires
- Intégration legacy : Réutilisation de code Perl existant
| Aspect | PL/pgSQL | PL/Perl | PL/Python |
|---|---|---|---|
| Point fort | SQL natif | Regex/Texte | Écosystème moderne |
| Performance SQL | Excellent | Bon | Bon |
| Regex | Basique | Excellent | Bon |
| Manipulation texte | Basique | Excellent | Bon |
| Courbe apprentissage | Moyenne | Moyenne-Élevée | Facile |
| Syntaxe | SQL-like | Perl | Python |
| Modules externes | Non | CPAN | pip/PyPI |
PostgreSQL propose deux variantes de PL/Perl :
-
plperl (Trusted) : Version "sécurisée"
- Pas d'accès aux fichiers système
- Opérations limitées
- Utilisateurs normaux peuvent créer des fonctions
- Modules CPAN restreints
-
plperlu (Untrusted) : Version "non sécurisée"
- Accès complet au système de fichiers
- Peut exécuter des commandes système
- Nécessite les privilèges superutilisateur
- Accès complet aux modules CPAN
- Recommandé pour les cas d'usage avancés
-- Version trusted (pour utilisateurs standards)
CREATE EXTENSION plperl;
-- Version untrusted (pour superutilisateurs, plus de liberté)
CREATE EXTENSION plperlu;Note importante :
plperletplperlupeuvent coexister- Les fonctions doivent spécifier le langage utilisé
- PL/Perl utilise la version de Perl installée sur le système
CREATE OR REPLACE FUNCTION perl_version()
RETURNS text AS $$
return $^V; # Variable spéciale Perl contenant la version
$$ LANGUAGE plperlu;
SELECT perl_version();
-- Résultat : "v5.36.0" (par exemple)CREATE OR REPLACE FUNCTION perl_modules_info()
RETURNS text AS $$
my $info = "Perl version: $^V\n";
$info .= "INC paths:\n";
foreach my $path (@INC) {
$info .= " - $path\n";
}
return $info;
$$ LANGUAGE plperlu;
SELECT perl_modules_info();CREATE OR REPLACE FUNCTION nom_fonction(param1 type1, param2 type2)
RETURNS type_retour AS $$
# Code Perl ici
# Les paramètres sont dans @_
my ($arg1, $arg2) = @_;
my $result = $arg1 + $arg2;
return $result;
$$ LANGUAGE plperlu;- Délimiteurs
$$: Encadrent le code Perl - Tableau
@_: Contient les arguments de la fonction - Variables
my: Déclaration de variables locales (bonne pratique) - Instruction
return: Retourne la valeur à PostgreSQL - Clause
LANGUAGE plperlu: Spécifie le langage
Il existe plusieurs façons d'accéder aux paramètres :
-- Méthode 1 : Décomposition du tableau @_
CREATE OR REPLACE FUNCTION methode1(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
my ($x, $y) = @_; # Décomposition
return $x + $y;
$$ LANGUAGE plperlu;
-- Méthode 2 : Accès direct par index
CREATE OR REPLACE FUNCTION methode2(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
return $_[0] + $_[1]; # Accès direct
$$ LANGUAGE plperlu;
-- Méthode 3 : Variables nommées (le plus lisible)
CREATE OR REPLACE FUNCTION methode3(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
my $premier = shift; # Récupère $_[0]
my $second = shift; # Récupère $_[1]
return $premier + $second;
$$ LANGUAGE plperlu;PostgreSQL convertit automatiquement les types entre SQL et Perl :
| Type PostgreSQL | Type Perl | Exemple | Note |
|---|---|---|---|
INTEGER, BIGINT |
Scalaire numérique | 42 |
Conversion automatique |
NUMERIC, DECIMAL |
Scalaire chaîne | "123.45" |
Précision préservée |
REAL, DOUBLE |
Scalaire numérique | 3.14 |
Float Perl |
TEXT, VARCHAR |
Scalaire chaîne | "Hello" |
Encodage UTF-8 |
BOOLEAN |
Scalaire | 1 ou 0 ou undef |
t/f en base |
DATE |
Scalaire chaîne | "2025-11-22" |
Format ISO |
TIMESTAMP |
Scalaire chaîne | "2025-11-22 14:30:00" |
Format ISO |
ARRAY |
Référence tableau | [1, 2, 3] |
Entre accolades en SQL |
NULL |
undef |
undef |
Valeur non définie |
CREATE OR REPLACE FUNCTION demo_types(
un_entier INTEGER,
un_texte TEXT,
une_date DATE,
un_tableau INTEGER[]
)
RETURNS TEXT AS $$
my ($entier, $texte, $date, $tableau) = @_;
# $entier est un nombre Perl
my $double = $entier * 2;
# $texte est une chaîne Perl
my $majuscules = uc($texte); # uc = uppercase
# $date est une chaîne au format ISO
my $annee = substr($date, 0, 4);
# $tableau est une référence à un tableau Perl
my $somme = 0;
foreach my $val (@$tableau) {
$somme += $val;
}
return "Double: $double, Texte: $majuscules, Année: $annee, Somme: $somme";
$$ LANGUAGE plperlu;
-- Utilisation
SELECT demo_types(10, 'postgresql', '2025-11-22', ARRAY[1,2,3,4,5]);
-- Résultat : "Double: 20, Texte: POSTGRESQL, Année: 2025, Somme: 15"CREATE OR REPLACE FUNCTION calculer_tva(prix_ht NUMERIC, taux_tva NUMERIC)
RETURNS NUMERIC AS $$
my ($ht, $taux) = @_;
# Calcul de la TVA
my $tva = $ht * ($taux / 100);
my $ttc = $ht + $tva;
return $ttc;
$$ LANGUAGE plperlu;
SELECT calculer_tva(100.00, 20.0);
-- Résultat : 120.00CREATE OR REPLACE FUNCTION formater_nom_complet(prenom TEXT, nom TEXT)
RETURNS TEXT AS $$
my ($prenom, $nom) = @_;
# Nettoyage et formatage
$prenom =~ s/^\s+|\s+$//g; # Trim
$nom =~ s/^\s+|\s+$//g;
$prenom = ucfirst(lc($prenom)); # Première lettre majuscule
$nom = uc($nom); # Tout en majuscules
return "$prenom $nom";
$$ LANGUAGE plperlu;
SELECT formater_nom_complet(' jean ', ' dupont ');
-- Résultat : "Jean DUPONT"CREATE OR REPLACE FUNCTION valider_email(email TEXT)
RETURNS BOOLEAN AS $$
my $email = shift;
return 0 unless defined $email; # Retour false si undef
# Regex pour validation email (simplifiée)
if ($email =~ /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/) {
return 1; # TRUE
} else {
return 0; # FALSE
}
$$ LANGUAGE plperlu;
SELECT valider_email('user@example.com'); -- TRUE
SELECT valider_email('invalid.email'); -- FALSE Perl est réputé pour avoir les expressions régulières les plus puissantes et flexibles.
CREATE OR REPLACE FUNCTION extraire_domaine_email(email TEXT)
RETURNS TEXT AS $$
my $email = shift;
# Extraction du domaine avec regex
if ($email =~ /@(.+)$/) {
return $1; # $1 contient le premier groupe capturé
}
return undef; # NULL en SQL
$$ LANGUAGE plperlu;
SELECT extraire_domaine_email('john.doe@example.com');
-- Résultat : "example.com"CREATE OR REPLACE FUNCTION anonymiser_texte(texte TEXT)
RETURNS TEXT AS $$
my $texte = shift;
# Remplacer tous les emails par [EMAIL]
$texte =~ s/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/[EMAIL]/g;
# Remplacer tous les numéros de téléphone (format FR)
$texte =~ s/\b0[1-9](?:\s?\d{2}){4}\b/[TELEPHONE]/g;
# Remplacer les numéros de carte bancaire (4 groupes de 4 chiffres)
$texte =~ s/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/[CARTE]/g;
return $texte;
$$ LANGUAGE plperlu;
SELECT anonymiser_texte('Contact: john@example.com ou 06 12 34 56 78');
-- Résultat : "Contact: [EMAIL] ou [TELEPHONE]"CREATE OR REPLACE FUNCTION extraire_hashtags(texte TEXT)
RETURNS TEXT[] AS $$
my $texte = shift;
# Trouver tous les hashtags (#mot)
my @hashtags = ($texte =~ /#(\w+)/g);
# Retourner une référence à un tableau
return \@hashtags;
$$ LANGUAGE plperlu;
SELECT extraire_hashtags('J''aime #PostgreSQL et #Perl pour le #dev');
-- Résultat : {PostgreSQL,Perl,dev}CREATE OR REPLACE FUNCTION valider_mot_de_passe(mdp TEXT)
RETURNS TABLE(valide BOOLEAN, message TEXT) AS $$
my $mdp = shift;
# Critères de validation
my @erreurs;
push @erreurs, "Minimum 8 caractères requis" if length($mdp) < 8;
push @erreurs, "Au moins une majuscule requise" unless $mdp =~ /[A-Z]/;
push @erreurs, "Au moins une minuscule requise" unless $mdp =~ /[a-z]/;
push @erreurs, "Au moins un chiffre requis" unless $mdp =~ /\d/;
push @erreurs, "Au moins un caractère spécial requis" unless $mdp =~ /[!@#$%^&*(),.?":{}|<>]/;
if (@erreurs) {
my $msg = join(", ", @erreurs);
return_next({valide => 0, message => $msg});
} else {
return_next({valide => 1, message => "Mot de passe valide"});
}
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM valider_mot_de_passe('faible');
-- valide | message
-- FALSE | Minimum 8 caractères requis, Au moins une majuscule requise, ...
SELECT * FROM valider_mot_de_passe('Fort123!Pass');
-- valide | message
-- TRUE | Mot de passe valideCREATE OR REPLACE FUNCTION parser_log_apache(ligne_log TEXT)
RETURNS TABLE(
ip TEXT,
date_acces TEXT,
methode TEXT,
url TEXT,
code_http INTEGER,
user_agent TEXT
) AS $$
my $log = shift;
# Regex pour log Apache format combiné
# Format: IP - - [Date] "METHOD URL PROTOCOL" CODE SIZE "REFERER" "USER-AGENT"
if ($log =~ /^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+) \S+ "([^"]*)" "([^"]*)"/) {
return_next({
ip => $1,
date_acces => $2,
methode => $3,
url => $4,
code_http => $5,
user_agent => $7
});
}
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM parser_log_apache(
'192.168.1.1 - - [22/Nov/2025:14:30:00 +0100] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0"'
);CREATE OR REPLACE FUNCTION extraire_infos_produit(description TEXT)
RETURNS TABLE(
reference TEXT,
prix NUMERIC,
quantite INTEGER
) AS $$
my $desc = shift;
my ($ref, $prix, $qte);
# Extraction de la référence (format: REF-XXXXX)
if ($desc =~ /REF-(\w+)/) {
$ref = $1;
}
# Extraction du prix (format: 123.45€ ou 123,45€)
if ($desc =~ /([\d,\.]+)\s*€/) {
$prix = $1;
$prix =~ s/,/./; # Remplacer virgule par point
}
# Extraction de la quantité (format: Qté: 10 ou x10)
if ($desc =~ /(?:Qté|x)\s*[:=]?\s*(\d+)/i) {
$qte = $1;
}
return_next({
reference => $ref,
prix => $prix,
quantite => $qte
});
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM extraire_infos_produit('Produit REF-A1234 à 49,99€ - Qté: 25');
-- reference | prix | quantite
-- A1234 | 49.99 | 25CREATE OR REPLACE FUNCTION nettoyer_texte(texte TEXT)
RETURNS TEXT AS $$
my $texte = shift;
# Supprimer les balises HTML
$texte =~ s/<[^>]+>//g;
# Supprimer les espaces multiples
$texte =~ s/\s+/ /g;
# Supprimer les espaces en début/fin
$texte =~ s/^\s+|\s+$//g;
# Remplacer les entités HTML courantes
$texte =~ s/ / /g;
$texte =~ s/&/&/g;
$texte =~ s/</</g;
$texte =~ s/>/>/g;
$texte =~ s/"/"/g;
return $texte;
$$ LANGUAGE plperlu;
SELECT nettoyer_texte('<p>Texte avec espaces & balises</p>');
-- Résultat : "Texte avec espaces & balises"CREATE OR REPLACE FUNCTION statistiques_tableau(valeurs INTEGER[])
RETURNS TABLE(
moyenne NUMERIC,
mediane NUMERIC,
minimum INTEGER,
maximum INTEGER,
total INTEGER
) AS $$
my $valeurs = shift;
# Déréférencement du tableau
my @array = @$valeurs;
return_next({
moyenne => undef,
mediane => undef,
minimum => undef,
maximum => undef,
total => 0
}) unless @array;
# Calculs
my $somme = 0;
$somme += $_ for @array;
my @sorted = sort { $a <=> $b } @array;
my $n = scalar @sorted;
my $mediane = $n % 2 ? $sorted[$n/2] : ($sorted[$n/2-1] + $sorted[$n/2]) / 2;
return_next({
moyenne => $somme / $n,
mediane => $mediane,
minimum => $sorted[0],
maximum => $sorted[-1],
total => $somme
});
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM statistiques_tableau(ARRAY[10, 20, 30, 40, 50]::INTEGER[]);CREATE OR REPLACE FUNCTION filtrer_pairs(valeurs INTEGER[])
RETURNS INTEGER[] AS $$
my $valeurs = shift;
my @array = @$valeurs;
# Filtrage avec grep (équivalent de filter en Python)
my @pairs = grep { $_ % 2 == 0 } @array;
return \@pairs;
$$ LANGUAGE plperlu;
SELECT filtrer_pairs(ARRAY[1,2,3,4,5,6,7,8,9,10]);
-- Résultat : {2,4,6,8,10}CREATE OR REPLACE FUNCTION compter_occurrences(mots TEXT[])
RETURNS TEXT AS $$
my $mots = shift;
my @array = @$mots;
# Hash pour compter les occurrences
my %compteur;
$compteur{$_}++ for @array;
# Construction du résultat
my @resultats;
foreach my $mot (sort keys %compteur) {
push @resultats, "$mot: $compteur{$mot}";
}
return join(", ", @resultats);
$$ LANGUAGE plperlu;
SELECT compter_occurrences(ARRAY['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']);
-- Résultat : "apple: 3, banana: 2, cherry: 1"L'un des avantages majeurs de PL/Perl (plperlu) est l'accès aux modules CPAN.
-- Test de disponibilité d'un module
CREATE OR REPLACE FUNCTION tester_module(nom_module TEXT)
RETURNS BOOLEAN AS $$
my $module = shift;
eval "require $module";
return $@ ? 0 : 1; # Retourne 0 si erreur, 1 si succès
$$ LANGUAGE plperlu;
SELECT tester_module('DBI');
SELECT tester_module('JSON');
SELECT tester_module('Time::Piece'); CREATE OR REPLACE FUNCTION parser_json_perl(json_text TEXT)
RETURNS TABLE(cle TEXT, valeur TEXT) AS $$
use JSON;
my $json_text = shift;
my $data = decode_json($json_text);
# Itération sur les clés
foreach my $key (keys %$data) {
return_next({
cle => $key,
valeur => $data->{$key}
});
}
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM parser_json_perl('{"nom": "Alice", "age": "30", "ville": "Paris"}');CREATE OR REPLACE FUNCTION analyser_date(date_str TEXT)
RETURNS TABLE(
date_formatee TEXT,
jour_semaine TEXT,
trimestre INTEGER,
est_weekend BOOLEAN
) AS $$
use Time::Piece;
my $date_str = shift;
my $t = Time::Piece->strptime($date_str, "%Y-%m-%d");
# Analyse de la date
my $jour_semaine = $t->fullday;
my $trimestre = int(($t->mon - 1) / 3) + 1;
my $est_weekend = ($t->wday == 0 || $t->wday == 6) ? 1 : 0;
return_next({
date_formatee => $t->strftime("%d/%m/%Y"),
jour_semaine => $jour_semaine,
trimestre => $trimestre,
est_weekend => $est_weekend
});
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM analyser_date('2025-11-22');
-- date_formatee | jour_semaine | trimestre | est_weekend
-- 22/11/2025 | Saturday | 4 | TRUECREATE OR REPLACE FUNCTION encoder_base64(texte TEXT)
RETURNS TEXT AS $$
use MIME::Base64;
my $texte = shift;
return encode_base64($texte, ''); # '' pour pas de retours à la ligne
$$ LANGUAGE plperlu;
CREATE OR REPLACE FUNCTION decoder_base64(encoded TEXT)
RETURNS TEXT AS $$
use MIME::Base64;
my $encoded = shift;
return decode_base64($encoded);
$$ LANGUAGE plperlu;
SELECT encoder_base64('PostgreSQL avec Perl');
-- Résultat : "UG9zdGdyZVNRTCBhdmVjIFBlcmw="
SELECT decoder_base64('UG9zdGdyZVNRTCBhdmVjIFBlcmw=');
-- Résultat : "PostgreSQL avec Perl"CREATE OR REPLACE FUNCTION generer_uuid_perl()
RETURNS TEXT AS $$
use Data::UUID;
my $ug = Data::UUID->new;
my $uuid = $ug->create_str();
return $uuid;
$$ LANGUAGE plperlu;
SELECT generer_uuid_perl();
-- Résultat : "550e8400-e29b-41d4-a716-446655440000" (exemple)Le module spi_* permet d'exécuter des requêtes SQL depuis le code Perl.
CREATE OR REPLACE FUNCTION compter_utilisateurs_perl()
RETURNS INTEGER AS $$
# Exécution d'une requête
my $rv = spi_exec_query("SELECT COUNT(*) as total FROM utilisateurs");
# Accès au résultat
return $rv->{rows}[0]->{total};
$$ LANGUAGE plperlu;CREATE OR REPLACE FUNCTION obtenir_utilisateur_par_id(user_id INTEGER)
RETURNS TABLE(id INTEGER, nom TEXT, email TEXT) AS $$
my $user_id = shift;
# Utilisation de quote_nullable pour sécurité
my $quoted_id = quote_nullable($user_id);
# Meilleure approche : requête préparée
my $plan = spi_prepare(
'SELECT id, nom, email FROM utilisateurs WHERE id = $1',
'INTEGER'
);
my $rv = spi_exec_prepared($plan, $user_id);
foreach my $row (@{$rv->{rows}}) {
return_next({
id => $row->{id},
nom => $row->{nom},
email => $row->{email}
});
}
return undef;
$$ LANGUAGE plperlu;CREATE OR REPLACE FUNCTION incrementer_compteur(cle TEXT)
RETURNS INTEGER AS $$
my $cle = shift;
# Vérifier si la clé existe
my $check_plan = spi_prepare(
'SELECT valeur FROM compteurs WHERE cle = $1',
'TEXT'
);
my $check_rv = spi_exec_prepared($check_plan, $cle);
if ($check_rv->{processed} > 0) {
# Mise à jour
my $update_plan = spi_prepare(
'UPDATE compteurs SET valeur = valeur + 1 WHERE cle = $1 RETURNING valeur',
'TEXT'
);
my $update_rv = spi_exec_prepared($update_plan, $cle);
return $update_rv->{rows}[0]->{valeur};
} else {
# Insertion
my $insert_plan = spi_prepare(
'INSERT INTO compteurs (cle, valeur) VALUES ($1, 1) RETURNING valeur',
'TEXT'
);
my $insert_rv = spi_exec_prepared($insert_plan, $cle);
return $insert_rv->{rows}[0]->{valeur};
}
$$ LANGUAGE plperlu;CREATE OR REPLACE FUNCTION generer_serie_perl(debut INTEGER, fin INTEGER)
RETURNS SETOF INTEGER AS $$
my ($debut, $fin) = @_;
for (my $i = $debut; $i <= $fin; $i++) {
return_next($i);
}
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM generer_serie_perl(1, 5);
-- 1
-- 2
-- 3
-- 4
-- 5CREATE OR REPLACE FUNCTION analyser_texte(texte TEXT)
RETURNS TABLE(
type_element TEXT,
valeur TEXT,
position INTEGER
) AS $$
my $texte = shift;
my $pos = 0;
# Extraction des emails
while ($texte =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g) {
return_next({
type_element => 'email',
valeur => $1,
position => pos($texte) - length($1)
});
}
# Extraction des URLs
while ($texte =~ /(https?:\/\/[^\s]+)/g) {
return_next({
type_element => 'url',
valeur => $1,
position => pos($texte) - length($1)
});
}
# Extraction des hashtags
while ($texte =~ /#(\w+)/g) {
return_next({
type_element => 'hashtag',
valeur => $1,
position => pos($texte) - length($1) - 1
});
}
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM analyser_texte('Visitez https://postgresql.org et contactez admin@example.com #PostgreSQL');CREATE OR REPLACE FUNCTION diviser_perl(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
my ($a, $b) = @_;
if ($b == 0) {
elog(ERROR, "Division par zéro interdite");
}
return $a / $b;
$$ LANGUAGE plperlu;CREATE OR REPLACE FUNCTION demo_messages_perl(valeur INTEGER)
RETURNS TEXT AS $$
my $valeur = shift;
# DEBUG : Message de débogage
elog(DEBUG, "Valeur reçue: $valeur");
# INFO : Message informatif
elog(INFO, "Traitement en cours...");
# NOTICE : Notification
elog(NOTICE, "Valeur validée");
# WARNING : Avertissement
if ($valeur > 100) {
elog(WARNING, "Valeur élevée détectée: $valeur");
}
# ERROR : Erreur (arrête l'exécution)
if ($valeur < 0) {
elog(ERROR, "Valeur négative non autorisée");
}
return "Traitement réussi";
$$ LANGUAGE plperlu;CREATE OR REPLACE FUNCTION operation_securisee(operation TEXT)
RETURNS TEXT AS $$
my $operation = shift;
my $result;
eval {
# Code potentiellement dangereux
$result = eval $operation;
};
if ($@) {
elog(WARNING, "Erreur lors de l'évaluation: $@");
return "ERREUR";
}
return "Résultat: $result";
$$ LANGUAGE plperlu;PL/Perl maintient des variables globales entre les appels de fonction dans la même session.
CREATE OR REPLACE FUNCTION compteur_appels()
RETURNS INTEGER AS $$
use vars qw(%_SHARED);
# Variable globale partagée entre les appels
$_SHARED{compteur} = 0 unless defined $_SHARED{compteur};
$_SHARED{compteur}++;
return $_SHARED{compteur};
$$ LANGUAGE plperlu;
-- Premier appel
SELECT compteur_appels(); -- Résultat : 1
-- Deuxième appel dans la même session
SELECT compteur_appels(); -- Résultat : 2CREATE OR REPLACE FUNCTION cache_config(cle TEXT, valeur TEXT)
RETURNS TEXT AS $$
use vars qw(%_SHARED);
my ($cle, $valeur) = @_;
# Initialisation du cache
$_SHARED{config_cache} = {} unless defined $_SHARED{config_cache};
if (defined $valeur) {
# Stockage dans le cache
$_SHARED{config_cache}{$cle} = $valeur;
return "Stocké: $cle = $valeur";
} else {
# Lecture du cache
if (exists $_SHARED{config_cache}{$cle}) {
return $_SHARED{config_cache}{$cle};
} else {
return undef;
}
}
$$ LANGUAGE plperlu;
-- Stockage
SELECT cache_config('app.version', '1.0.0');
-- Lecture
SELECT cache_config('app.version', NULL);
-- Résultat : "1.0.0"CREATE OR REPLACE FUNCTION parser_csv_ligne(ligne TEXT, separateur TEXT DEFAULT ',')
RETURNS TEXT[] AS $$
use Text::CSV;
my ($ligne, $sep) = @_;
$sep = ',' unless defined $sep;
my $csv = Text::CSV->new({ sep_char => $sep });
if ($csv->parse($ligne)) {
my @fields = $csv->fields();
return \@fields;
} else {
elog(ERROR, "Erreur de parsing CSV: " . $csv->error_diag());
}
$$ LANGUAGE plperlu;
SELECT parser_csv_ligne('John,Doe,30,New York', ',');
-- Résultat : {John,Doe,30,"New York"}CREATE OR REPLACE FUNCTION generer_slug(titre TEXT)
RETURNS TEXT AS $$
my $titre = shift;
# Conversion en minuscules
$titre = lc($titre);
# Remplacement des accents
$titre =~ tr/àáâãäåçèéêëìíîïñòóôõöùúûüýÿ/aaaaaaceeeeiiiinooooouuuuyy/;
# Remplacement des caractères non alphanumériques par des tirets
$titre =~ s/[^a-z0-9]+/-/g;
# Suppression des tirets en début/fin
$titre =~ s/^-+|-+$//g;
return $titre;
$$ LANGUAGE plperlu;
SELECT generer_slug('Maîtriser PostgreSQL 18 : Guide Complet !');
-- Résultat : "maitriser-postgresql-18-guide-complet"CREATE OR REPLACE FUNCTION parser_user_agent(ua TEXT)
RETURNS TABLE(
navigateur TEXT,
version TEXT,
os TEXT,
est_mobile BOOLEAN
) AS $$
my $ua = shift;
my ($browser, $version, $os);
my $mobile = 0;
# Détection du navigateur
if ($ua =~ /Firefox\/(\S+)/) {
$browser = "Firefox";
$version = $1;
} elsif ($ua =~ /Chrome\/(\S+)/) {
$browser = "Chrome";
$version = $1;
} elsif ($ua =~ /Safari\/(\S+)/) {
$browser = "Safari";
$version = $1;
} elsif ($ua =~ /MSIE (\S+)/) {
$browser = "Internet Explorer";
$version = $1;
}
# Détection de l'OS
if ($ua =~ /Windows/) {
$os = "Windows";
} elsif ($ua =~ /Mac OS X/) {
$os = "MacOS";
} elsif ($ua =~ /Linux/) {
$os = "Linux";
} elsif ($ua =~ /Android/) {
$os = "Android";
$mobile = 1;
} elsif ($ua =~ /iPhone|iPad/) {
$os = "iOS";
$mobile = 1;
}
return_next({
navigateur => $browser,
version => $version,
os => $os,
est_mobile => $mobile
});
return undef;
$$ LANGUAGE plperlu;
SELECT * FROM parser_user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36');CREATE OR REPLACE FUNCTION nettoyer_numero_telephone(numero TEXT)
RETURNS TEXT AS $$
my $numero = shift;
# Suppression de tous les caractères non numériques
$numero =~ s/\D//g;
# Validation longueur (France : 10 chiffres)
return undef unless length($numero) == 10;
# Vérification que ça commence par 0
return undef unless $numero =~ /^0/;
# Formatage : 01 23 45 67 89
$numero =~ s/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/$1 $2 $3 $4 $5/;
return $numero;
$$ LANGUAGE plperlu;
SELECT nettoyer_numero_telephone('01.23.45.67.89');
-- Résultat : "01 23 45 67 89"
SELECT nettoyer_numero_telephone('+33 1 23 45 67 89');
-- Résultat : NULL (car > 10 chiffres après nettoyage)Toujours utiliser spi_prepare pour les requêtes SQL :
-- ❌ DANGEREUX - Injection SQL
CREATE OR REPLACE FUNCTION recherche_danger(nom TEXT)
RETURNS INTEGER AS $$
my $nom = shift;
my $query = "SELECT COUNT(*) as cnt FROM users WHERE nom = '$nom'";
my $rv = spi_exec_query($query);
return $rv->{rows}[0]->{cnt};
$$ LANGUAGE plperlu;
-- ✅ SÉCURISÉ - Requête préparée
CREATE OR REPLACE FUNCTION recherche_securisee(nom TEXT)
RETURNS INTEGER AS $$
my $nom = shift;
my $plan = spi_prepare('SELECT COUNT(*) as cnt FROM users WHERE nom = $1', 'TEXT');
my $rv = spi_exec_prepared($plan, $nom);
return $rv->{rows}[0]->{cnt};
$$ LANGUAGE plperlu;Optimiser les expressions régulières :
-- Compiler les regex fréquentes
CREATE OR REPLACE FUNCTION valider_format(texte TEXT, pattern TEXT)
RETURNS BOOLEAN AS $$
use vars qw(%_SHARED);
my ($texte, $pattern) = @_;
# Cache des regex compilées
unless (exists $_SHARED{regex_cache}{$pattern}) {
$_SHARED{regex_cache}{$pattern} = qr/$pattern/;
}
my $regex = $_SHARED{regex_cache}{$pattern};
return $texte =~ $regex ? 1 : 0;
$$ LANGUAGE plperlu;Utiliser des variables nommées :
-- ❌ Moins lisible
CREATE OR REPLACE FUNCTION calcul_remise_mauvais(prix NUMERIC, pct NUMERIC)
RETURNS NUMERIC AS $$
return $_[0] * (1 - $_[1] / 100);
$$ LANGUAGE plperlu;
-- ✅ Plus lisible
CREATE OR REPLACE FUNCTION calcul_remise_bon(prix NUMERIC, pct NUMERIC)
RETURNS NUMERIC AS $$
my ($prix, $pourcentage) = @_;
my $remise = $prix * ($pourcentage / 100);
my $prix_final = $prix - $remise;
return $prix_final;
$$ LANGUAGE plperlu;Toujours valider les entrées :
CREATE OR REPLACE FUNCTION fonction_robuste(valeur TEXT)
RETURNS TEXT AS $$
my $valeur = shift;
# Validation des entrées
return undef unless defined $valeur;
return undef if $valeur eq '';
# Traitement avec gestion d'erreur
my $result;
eval {
$result = uc($valeur);
};
if ($@) {
elog(WARNING, "Erreur de traitement: $@");
return undef;
}
return $result;
$$ LANGUAGE plperlu;CREATE OR REPLACE FUNCTION fonction_documentee(param TEXT)
RETURNS TEXT AS $$
# Description : Cette fonction fait XYZ
#
# Arguments :
# param (TEXT) : Description du paramètre
#
# Retourne :
# TEXT : Description du retour
#
# Exemple :
# SELECT fonction_documentee('test');
#
# Notes :
# - Point important 1
# - Point important 2
my $param = shift;
return uc($param);
$$ LANGUAGE plperlu;
-- Commentaire PostgreSQL
COMMENT ON FUNCTION fonction_documentee(TEXT) IS
'Convertit le texte en majuscules avec validation';- Performance : Plus lent que PL/pgSQL pour les opérations SQL simples
- Mémoire : Charge mémoire plus importante
- Pas de parallélisation : Les fonctions ne peuvent pas être parallélisées
- Interpréteur unique : Un seul par backend PostgreSQL
- plperlu est "untrusted" : Accès complet au système de fichiers
- Privilèges superutilisateur : Nécessaires pour créer des fonctions
- Modules CPAN : Attention aux dépendances et vulnérabilités
- Exécution de code : Risque si les entrées ne sont pas validées
- Dépendances CPAN : Peuvent nécessiter des installations système
- Portabilité : Dépend de la version Perl et des modules installés
- Tests : Plus complexes que pour du SQL pur
- Débogage : Moins d'outils natifs
| Critère | Perl | Python |
|---|---|---|
| Regex | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐ Bon |
| Manipulation texte | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐ Très bon |
| Lisibilité | ⭐⭐⭐ Bonne | ⭐⭐⭐⭐⭐ Excellente |
| Écosystème moderne | ⭐⭐⭐ CPAN | ⭐⭐⭐⭐⭐ PyPI |
| Communauté | ⭐⭐⭐ Mature | ⭐⭐⭐⭐⭐ Très active |
-
Traitement de texte complexe
- Parsing de logs
- Extraction de données non structurées
- Nettoyage et transformation de texte
-
Expressions régulières avancées
- Validation de formats complexes
- Extraction de patterns sophistiqués
- Recherche et remplacement multiples
-
Manipulation de formats propriétaires
- CSV, XML personnalisés
- Formats legacy
- Parsing de fichiers de configuration
-
Intégration de code existant
- Réutilisation de bibliothèques Perl
- Migration de scripts Perl
- Compatibilité avec systèmes legacy
-
Accès CPAN spécifique
- Modules Perl uniques
- Fonctionnalités non disponibles en Python
- Compatibilité avec outils Perl
-
Opérations SQL intensives
- Privilégier PL/pgSQL
- Meilleure intégration native
-
Projets nécessitant une large équipe
- Perl moins populaire aujourd'hui
- Difficulté de recrutement
- Courbe d'apprentissage
-
Applications modernes
- Python plus moderne
- Meilleur support IA/ML
- Écosystème plus actif
-- PL/pgSQL : Le natif
CREATE OR REPLACE FUNCTION exemple_plpgsql(texte TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN upper(texte);
END;
$$ LANGUAGE plpgsql;
-- PL/Perl : Le spécialiste texte/regex
CREATE OR REPLACE FUNCTION exemple_plperl(texte TEXT)
RETURNS TEXT AS $$
my $texte = shift;
$texte =~ s/\s+/ /g; # Nettoyage espaces
return uc($texte); # Majuscules
$$ LANGUAGE plperlu;
-- PL/Python : Le moderne
CREATE OR REPLACE FUNCTION exemple_plpython(texte TEXT)
RETURNS TEXT AS $$
import re
texte = re.sub(r'\s+', ' ', texte)
return texte.upper()
$$ LANGUAGE plpython3u;Recommandations :
- PL/pgSQL : Par défaut pour toute logique SQL
- PL/Perl : Pour regex complexes et parsing texte intensif
- PL/Python : Pour logique métier moderne et intégrations
- PostgreSQL PL/Perl : https://www.postgresql.org/docs/current/plperl.html
- Perl 5 Documentation : https://perldoc.perl.org/
- CPAN : https://metacpan.org/
- Text::CSV : Manipulation CSV
- JSON : Parsing/génération JSON
- Time::Piece : Manipulation dates
- LWP::UserAgent : Requêtes HTTP
- DBI : Interface bases de données
- XML::Simple : Parsing XML
- Digest::SHA : Hash et cryptographie
- MIME::Base64 : Encodage/décodage
- "Programming Perl" (Larry Wall) - La référence
- "Mastering Regular Expressions" (Jeffrey Friedl)
- "Perl Best Practices" (Damian Conway)
PL/Perl (plperlu) est un langage procédural puissant qui :
✅ Forces :
- Expressions régulières incomparables
- Manipulation de texte exceptionnelle
- Accès complet à CPAN
- Excellent pour parsing et transformation
- Code Perl réutilisable
❌ Faiblesses :
- Moins populaire que Python aujourd'hui
- Syntaxe considérée comme complexe
- Performance inférieure à PL/pgSQL pour SQL pur
- Communauté plus petite
- Maintenance plus difficile
Verdict : Utilisez PL/Perl quand vous avez besoin de ses forces uniques (regex, texte), sinon préférez PL/pgSQL (natif) ou PL/Python (moderne).