Skip to content

Latest commit

 

History

History
1429 lines (1084 loc) · 35.2 KB

File metadata and controls

1429 lines (1084 loc) · 35.2 KB

🔝 Retour au Sommaire

15.7.2. PL/Perl (plperlu)

Introduction à PL/Perl

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.

Qu'est-ce que Perl ?

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

Pourquoi utiliser PL/Perl ?

  • 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

PL/Perl vs PL/pgSQL vs PL/Python

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

Installation et Configuration

Les variantes de PL/Perl

PostgreSQL propose deux variantes de PL/Perl :

  1. 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
  2. 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

Installation de l'extension

-- Version trusted (pour utilisateurs standards)
CREATE EXTENSION plperl;

-- Version untrusted (pour superutilisateurs, plus de liberté)
CREATE EXTENSION plperlu;

Note importante :

  • plperl et plperlu peuvent coexister
  • Les fonctions doivent spécifier le langage utilisé
  • PL/Perl utilise la version de Perl installée sur le système

Vérification de la version Perl

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)

Vérification des modules disponibles

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

Anatomie d'une Fonction PL/Perl

Structure de base

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;

Éléments clés

  1. Délimiteurs $$ : Encadrent le code Perl
  2. Tableau @_ : Contient les arguments de la fonction
  3. Variables my : Déclaration de variables locales (bonne pratique)
  4. Instruction return : Retourne la valeur à PostgreSQL
  5. Clause LANGUAGE plperlu : Spécifie le langage

Accès aux paramètres

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;

Types de Données : PostgreSQL ↔ Perl

Conversion automatique

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

Exemple de conversion

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"

Fonctions Simples : Exemples Pratiques

Exemple 1 : Calcul mathématique

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.00

Exemple 2 : Manipulation de texte

CREATE 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"

Exemple 3 : Validation avec regex

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  

Expressions Régulières : La Force de Perl

Perl est réputé pour avoir les expressions régulières les plus puissantes et flexibles.

Recherche et matching

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"

Remplacement avec regex

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]"

Extraction de multiples occurrences

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}

Validation complexe

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 valide

Parsing et Traitement de Texte

Parsing de logs

CREATE 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"'
);

Extraction de données structurées

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 | 25

Nettoyage de texte

CREATE 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/&nbsp;/ /g;
    $texte =~ s/&amp;/&/g;
    $texte =~ s/&lt;/</g;
    $texte =~ s/&gt;/>/g;
    $texte =~ s/&quot;/"/g;

    return $texte;
$$ LANGUAGE plperlu;

SELECT nettoyer_texte('<p>Texte   avec   &nbsp;  espaces &amp; balises</p>');
-- Résultat : "Texte avec espaces & balises"

Manipulation de Tableaux et Structures

Traitement de tableaux

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[]);

Filtrage et transformation

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}

Construction de hash (dictionnaire)

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"

Utilisation de Modules CPAN

L'un des avantages majeurs de PL/Perl (plperlu) est l'accès aux modules CPAN.

Modules préinstallés courants

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

Manipulation JSON

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

Manipulation de dates

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         | TRUE

Encodage/Décodage Base64

CREATE 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"

Génération UUID

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)

Exécution de Requêtes SQL depuis Perl

Le module spi_* permet d'exécuter des requêtes SQL depuis le code Perl.

Exécution simple

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;

Requêtes préparées

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;

Modification de données

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;

Fonctions Retournant Plusieurs Lignes

Méthode return_next

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
-- 5

Table complexe

CREATE 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');

Gestion des Erreurs

Lever des exceptions

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;

Niveaux de messages

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;

Gestion avec eval

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;

Variables Globales et État Partagé

Variables globales

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 : 2

Cache de données

CREATE 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"

Cas d'Usage Avancés

1. Parsing de fichiers CSV

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"}

2. Génération de slugs URL

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"

3. Extraction d'informations de User-Agent

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

4. Validation et nettoyage de données

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)

Bonnes Pratiques

1. Sécurité

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;

2. Performance

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;

3. Code lisible

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;

4. Gestion d'erreurs

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;

5. Documentation

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

Limitations et Considérations

1. Limitations techniques

  • 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

2. Sécurité

  • 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

3. Maintenance

  • 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

4. Perl vs Python

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

Quand Utiliser PL/Perl ?

✅ Cas d'usage recommandés

  1. Traitement de texte complexe

    • Parsing de logs
    • Extraction de données non structurées
    • Nettoyage et transformation de texte
  2. Expressions régulières avancées

    • Validation de formats complexes
    • Extraction de patterns sophistiqués
    • Recherche et remplacement multiples
  3. Manipulation de formats propriétaires

    • CSV, XML personnalisés
    • Formats legacy
    • Parsing de fichiers de configuration
  4. Intégration de code existant

    • Réutilisation de bibliothèques Perl
    • Migration de scripts Perl
    • Compatibilité avec systèmes legacy
  5. Accès CPAN spécifique

    • Modules Perl uniques
    • Fonctionnalités non disponibles en Python
    • Compatibilité avec outils Perl

❌ Cas d'usage déconseillés

  1. Opérations SQL intensives

    • Privilégier PL/pgSQL
    • Meilleure intégration native
  2. Projets nécessitant une large équipe

    • Perl moins populaire aujourd'hui
    • Difficulté de recrutement
    • Courbe d'apprentissage
  3. Applications modernes

    • Python plus moderne
    • Meilleur support IA/ML
    • Écosystème plus actif

Comparaison Finale : PL/pgSQL vs PL/Perl vs PL/Python

-- 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

Ressources et Documentation

Documentation officielle

Modules CPAN utiles

  • 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

Livres recommandés

  • "Programming Perl" (Larry Wall) - La référence
  • "Mastering Regular Expressions" (Jeffrey Friedl)
  • "Perl Best Practices" (Damian Conway)

Résumé

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).


⏭️ PL/v8 (JavaScript) - mention