Cette section explicite les différences entre le PL/pgSQL de PostgreSQL et le langage PL/SQL d'Oracle, afin d'aider les développeurs qui portent des applications d'Oracle® vers PostgreSQL.
PL/pgSQL est similaire à PL/SQL sur de nombreux aspects. C'est un langage itératif structuré en blocs et toutes les variables doivent être déclarées. Les affectations, boucles, conditionnelles sont similaires. Les principales différences que vous devez garder à l'esprit quand vous portez de PL/SQL vers PL/pgSQL sont:
Si un nom utilisé dans une commande SQL peut être soit un nom de colonne
d'une table utilisé dans la commande soit une référence à une variable
de la fonction, PL/SQL le traite comme un nom
de colonne. Par défaut, PL/pgSQL renverra une
erreur se plaignant que le nom est ambigu. Vous pouvez indiquer
plpgsql.variable_conflict
=
use_column
, pour changer ce comportement et
correspondre à PL/SQL, comme expliqué dans
Section 43.11.1. Il est préféreable d'éviter de tels
ambigüités des le début mais si vous devez migrer une grande quantité de
code qui dépend de ce comportement, paramétrer
variable_conflict
peut s'avérer être la meilleure
solution.
Dans PostgreSQL, le corps de la fonction doit être écrit comme une chaîne litérale. Du coup, vous avez besoin d'utiliser les guillemets dollar ou l'échappement des simples guillemets dans le corps de la fonction. Voir la Section 43.12.1.
Les noms de type de données ont besoin d'une conversion. Par exemple, les
valeurs de type chaîne de caractères sont souvent déclarées de type
varchar2
, qui n'est pas un type standard. Avec
PostgreSQL, utilisez à la place le type
varchar
ou text
. De la même façon, remplacez le
type number
avec numeric
, ou utilisez un autre
type de données numériques s'il en existe un plus approprié.
À la place des packages, utilisez des schémas pour organiser vos fonctions en groupes.
Comme il n'y a pas de paquetages, il n'y a pas non plus de variables au niveau paquetage. Ceci est un peu ennuyant. Vous pourriez être capable de conserver un état par session dans les tables temporaires à la place.
Les boucles FOR
d'entiers en ordre inverse
(REVERSE
) fonctionnent différemment ;
PL/SQL compte du second numéro jusqu'au
premier alors que PL/pgSQL compte du
premier jusqu'au second, ceci réclamant que les limites de la
boucle soient échangées lors du portage. Cette incompatibilité
est malheureuse mais a peu de chance d'être changée. (Voir Section 43.6.5.5.)
Les boucles FOR
sur des requêtes (autres que des
curseurs) fonctionnent aussi différemment : la variable cible doit
avoir été déclarée alors que PL/SQL les déclare
toujours implicitement. Un avantage de ceci est que les valeurs des
variables sont toujours accessibles à la sortie de la boucle.
Il existe plusieurs différences de notation pour l'utilisation des variables curseurs.
L'Exemple 43.9 montre comment porter une simple fonction de PL/SQL vers PL/pgSQL.
Exemple 43.9. Portage d'une fonction simple de PL/SQL vers PL/pgSQL
Voici une fonction en PL/SQL Oracle :
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar2, v_version varchar2) RETURN varchar2 IS BEGIN IF v_version IS NULL THEN RETURN v_name; END IF; RETURN v_name || '/' || v_version; END; / show errors;
Parcourons cette fonction et voyons les différences avec PL/pgSQL :
Le nom du type varchar2
a dû être changé en
varchar
ou text
. Dans les exemples de cette
section, nous utiliserons varchar
mais text
est souvent un meilleur choix si nous n'avons pas besoin de limite
spécifique de taille.
Le mot clé RETURN
dans le prototype de la fonction (pas dans
le corps de la fonction) devient RETURNS
dans PostgreSQL.
De plus, IS
devient AS
et vous avez besoin d'ajouter
une clause LANGUAGE
parce que PL/pgSQL n'est pas
le seul langage de procédures disponible.
Dans PostgreSQL, le corps de la fonction est
considéré comme une chaîne littérale, donc vous avez besoin d'utiliser
les guillemets simples ou les guillemets dollar tout autour. Ceci se
substitue au /
de fin dans l'approche d'Oracle.
La commande show errors
n'existe pas dans
PostgreSQL et n'est pas nécessaire car les erreurs
sont rapportées automatiquement.
Voici de quoi aurait l'air cette fonction portée sous PostgreSQL :
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar, v_version varchar) RETURNS varchar AS $$ BEGIN IF v_version IS NULL THEN return v_name; END IF; RETURN v_name || '/' || v_version; END; $$ LANGUAGE plpgsql;
L'Exemple 43.10 montre comment porter une fonction qui crée une autre fonction et comment gérer les problèmes de guillemets résultants.
Exemple 43.10. Portage d'une fonction qui crée une autre fonction de PL/SQL vers PL/pgSQL
La procédure suivante récupère des lignes d'une instruction SELECT
et construit une grande fonction dont les résultats sont dans une instruction
IF
pour favoriser l'efficacité.
Voici la version Oracle :
CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS referrer_keys CURSOR IS SELECT * FROM cs_referrer_keys ORDER BY try_order; func_cmd VARCHAR(4000); BEGIN func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR2, v_domain IN VARCHAR2, v_url IN VARCHAR2) RETURN VARCHAR2 IS BEGIN'; FOR referrer_key IN referrer_keys LOOP func_cmd := func_cmd || ' IF v_' || referrer_key.kind || ' LIKE ''' || referrer_key.key_string || ''' THEN RETURN ''' || referrer_key.referrer_type || '''; END IF;'; END LOOP; func_cmd := func_cmd || ' RETURN NULL; END;'; EXECUTE IMMEDIATE func_cmd; END; / show errors;
Voici comment la fonction serait dans PostgreSQL :
CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$ DECLARE CURSOR referrer_keys IS SELECT * FROM cs_referrer_keys ORDER BY try_order; func_body text; func_cmd text; BEGIN func_body := 'BEGIN' ; FOR referrer_key IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP func_body := func_body || ' IF v_' || referrer_key.kind || ' LIKE ' || quote_literal(referrer_key.key_string) || ' THEN RETURN ' || quote_literal(referrer_key.referrer_type) || '; END IF;' ; END LOOP; func_body := func_body || ' RETURN NULL; END;'; func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host varchar, v_domain varchar, v_url varchar) RETURNS varchar AS ' || quote_literal(func_body) || ' LANGUAGE plpgsql;' ; EXECUTE func_cmd; END; $func$ LANGUAGE plpgsql;
Notez comment le corps de la fonction est construit séparément et est passé
au travers de quote_literal
pour doubler tout symbole
guillemet qu'il peut contenir. Cette technique est nécessaire parce que
nous ne pouvons pas utiliser à coup sûr les guillemets dollar pour
définir la nouvelle fonction : nous ne sommes pas sûr de savoir
quelle chaîne sera interpolée à partir du champ
referrer_key.key_string
(nous supposons ici que ce
referrer_key.kind
vaut à coup sûr host
,
domain
ou url
mais
referrer_key.key_string
pourrait valoir autre chose, il
pourrait contenir en particulier des signes dollar). Cette fonction est
en fait une amélioration de l'original Oracle parce qu'il ne
génèrera pas de code cassé quand referrer_key.key_string
ou referrer_key.referrer_type
contient des guillemets.
L'Exemple 43.11 montre comment porter une fonction
ayant des paramètres OUT
et effectuant des manipulations de
chaînes.
PostgreSQL n'a pas de fonction instr
intégrée mais vous pouvez en créer une en utilisant une combinaison
d'autres fonctions. Dans la Section 43.13.3, il
y a une implémentation PL/pgSQL
d'instr
que vous pouvez utiliser pour faciliter votre
portage.
Exemple 43.11. Portage d'une procédure avec manipulation de chaînes et paramètres
OUT
de PL/SQL vers
PL/pgSQL
La procédure Oracle suivante est utilisée pour analyser une URL et renvoyer plusieurs éléments (hôte, chemin et requête). Les fonctions PL/pgSQL ne peuvent renvoyer qu'une seule valeur.
Voici la version Oracle :
CREATE OR REPLACE PROCEDURE cs_parse_url( v_url IN VARCHAR2, v_host OUT VARCHAR2, -- Celle-ci sera passée en retour v_path OUT VARCHAR2, -- Celle-là aussi v_query OUT VARCHAR2) -- Et celle-là IS a_pos1 INTEGER; a_pos2 INTEGER; BEGIN v_host := NULL; v_path := NULL; v_query := NULL; a_pos1 := instr(v_url, '//'); IF a_pos1 = 0 THEN RETURN; END IF; a_pos2 := instr(v_url, '/', a_pos1 + 2); IF a_pos2 = 0 THEN v_host := substr(v_url, a_pos1 + 2); v_path := '/'; RETURN; END IF; v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2); a_pos1 := instr(v_url, '?', a_pos2 + 1); IF a_pos1 = 0 THEN v_path := substr(v_url, a_pos2); RETURN; END IF; v_path := substr(v_url, a_pos2, a_pos1 - a_pos2); v_query := substr(v_url, a_pos1 + 1); END; / show errors;
Voici une traduction possible en PL/pgSQL :
CREATE OR REPLACE FUNCTION cs_parse_url( v_url IN VARCHAR, v_host OUT VARCHAR, -- This will be passed back v_path OUT VARCHAR, -- This one too v_query OUT VARCHAR) -- And this one AS $$ DECLARE a_pos1 INTEGER; a_pos2 INTEGER; BEGIN v_host := NULL; v_path := NULL; v_query := NULL; a_pos1 := instr(v_url, '//'); IF a_pos1 = 0 THEN RETURN; END IF; a_pos2 := instr(v_url, '/', a_pos1 + 2); IF a_pos2 = 0 THEN v_host := substr(v_url, a_pos1 + 2); v_path := '/'; RETURN; END IF; v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2); a_pos1 := instr(v_url, '?', a_pos2 + 1); IF a_pos1 = 0 THEN v_path := substr(v_url, a_pos2); RETURN; END IF; v_path := substr(v_url, a_pos2, a_pos1 - a_pos2); v_query := substr(v_url, a_pos1 + 1); END; $$ LANGUAGE plpgsql;
Cette fonction pourrait être utilisée ainsi :
SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');
L'Exemple 43.12 montre comment porter une procédure qui utilise de nombreuses fonctionnalités spécifiques à Oracle.
Exemple 43.12. Portage d'une procédure de PL/SQL vers PL/pgSQL
La version Oracle :
CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS a_running_job_count INTEGER; BEGIN LOCK TABLE cs_jobs IN EXCLUSIVE MODE; SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL; IF a_running_job_count > 0 THEN COMMIT; -- free lock raise_application_error(-20000, 'Unable to create a new job: a job is currently running.'); END IF; DELETE FROM cs_active_job; INSERT INTO cs_active_job(job_id) VALUES (v_job_id); BEGIN INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, sysdate); EXCEPTION WHEN dup_val_on_index THEN NULL; -- ne vous inquietez pas si cela existe déjà END; COMMIT; END; / show errors
Voici comment nous pourrions porter cette procédure vers PL/pgSQL :
CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id integer) AS $$ DECLARE a_running_job_count integer; BEGIN LOCK TABLE cs_jobs IN EXCLUSIVE MODE; SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL; IF a_running_job_count > 0 THEN COMMIT; -- free lock RAISE EXCEPTION 'Unable to create a new job: a job is currently running'; -- (1) END IF; DELETE FROM cs_active_job; INSERT INTO cs_active_job(job_id) VALUES (v_job_id); BEGIN INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now()); EXCEPTION WHEN unique_violation THEN -- (2) -- ne vous inquietez pas si cela existe déjà END; COMMIT; END; $$ LANGUAGE plpgsql;
La syntaxe de | |
Les noms d'exceptions supportées par PL/pgSQL sont différents de ceux d'Oracle. L'ensemble de noms d'exceptions intégré est plus important (voir l'Annexe A). Il n'existe actuellement pas de façon de déclarer des noms d'exceptions définis par l'utilisateur, bien que vous puissiez aussi ignorer les valeurs SQLSTATE choisies par l'utilisateur. |
Cette section explique quelques autres choses à surveiller quand on effectue un portage de fonctions PL/SQL Oracle vers PostgreSQL.
Dans PL/pgSQL, quand une exception est récupérée par une
clause EXCEPTION
, toutes les modifications de la base de
données depuis le bloc BEGIN
sont automatiquement annulées.
C'est-à-dire que le comportement est identique à celui obtenu à partir
d'Oracle avec :
BEGIN SAVEPOINT s1; ... code ici ... EXCEPTION WHEN ... THEN ROLLBACK TO s1; ... code ici ... WHEN ... THEN ROLLBACK TO s1; ... code ici ... END;
Si vous traduisez une procédure d'Oracle qui utilise
SAVEPOINT
et ROLLBACK TO
dans ce style, votre
tâche est facile : omettez SAVEPOINT
et
ROLLBACK TO
. Si vous avez une procédure qui utilise
SAVEPOINT
et ROLLBACK TO
d'une façon différente,
alors un peu de réflexion supplémentaire sera nécessaire.
EXECUTE
La version PL/pgSQL d'EXECUTE
fonctionne de façon
similaire à la version PL/SQL mais vous devez vous rappeler
d'utiliser quote_literal
et
quote_ident
comme décrit dans la Section 43.5.4. Les constructions de type
EXECUTE 'SELECT * FROM $1';
ne fonctionneront pas de
façon fiable à moins d'utiliser ces fonctions.
PostgreSQL vous donne deux modificateurs de création de fonctions pour optimiser l'exécution : la « volatilité » (la fonction renvoie toujours le même résultat quand on lui donne les mêmes arguments) et la « rigueur » (une fonction renvoie NULL si tous ses arguments sont NULL). Consultez la page de référence de CREATE FUNCTION pour les détails.
Pour faire usage de ces attributs d'optimisation, votre instruction
CREATE FUNCTION
devrait ressembler à ceci :
CREATE FUNCTION foo(...) RETURNS integer AS $$ ... $$ LANGUAGE plpgsql STRICT IMMUTABLE;
Cette section contient le code d'un ensemble de fonctions instr
compatible Oracle que vous pouvez utiliser pour simplifier vos efforts de portage.
-- -- fonctions instr qui reproduisent l'équivalent Oracle -- Syntaxe : instr(string1, string2 [, n [, m]]) -- où [] signifie paramètre optionnel. -- -- Cherche string1 en commençant par le n-ième caractère pour la m-ième occurrence -- de string2. Si n est négatif, cherche en sens inverse, en commençant au caractère -- en position abs(n) à partir de la fin de string1. -- If n n'est pas fourni, suppose 1 (la recherche commence au premier caractère). -- Si m n'est pas fourni, suppose 1 (la recherche commence au premier caractère). -- Renvoie l'index de début de string2 dans string1, ou 0 si string2 n'est pas trouvé. -- CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $$ BEGIN RETURN instr($1, $2, 1); END; $$ LANGUAGE plpgsql STRICT IMMUTABLE; CREATE FUNCTION instr(string varchar, string_to_search_for varchar, beg_index integer) RETURNS integer AS $$ DECLARE pos integer NOT NULL DEFAULT 0; temp_str varchar; beg integer; length integer; ss_length integer; BEGIN IF beg_index > 0 THEN temp_str := substring(string FROM beg_index); pos := position(string_to_search_for IN temp_str); IF pos = 0 THEN RETURN 0; ELSE RETURN pos + beg_index - 1; END IF; ELSIF beg_index < 0 THEN ss_length := char_length(string_to_search_for); length := char_length(string); beg := length + 1 + beg_index; WHILE beg > 0 LOOP temp_str := substring(string FROM beg FOR ss_length); IF string_to_search_for = temp_str THEN RETURN beg; END IF; beg := beg - 1; END LOOP; RETURN 0; ELSE RETURN 0; END IF; END; $$ LANGUAGE plpgsql STRICT IMMUTABLE; CREATE FUNCTION instr(string varchar, string_to_search_for varchar, beg_index integer, occur_index integer) RETURNS integer AS $$ DECLARE pos integer NOT NULL DEFAULT 0; occur_number integer NOT NULL DEFAULT 0; temp_str varchar; beg integer; i integer; length integer; ss_length integer; BEGIN IF occur_index <= 0 THEN RAISE 'argument ''%'' is out of range', occur_index USING ERRCODE = '22003'; END IF; IF beg_index > 0 THEN beg := beg_index - 1; FOR i IN 1..occur_index LOOP temp_str := substring(string FROM beg + 1); pos := position(string_to_search_for IN temp_str); IF pos = 0 THEN RETURN 0; END IF; beg := beg + pos; END LOOP; RETURN beg; ELSIF beg_index < 0 THEN ss_length := char_length(string_to_search_for); length := char_length(string); beg := length + 1 + beg_index; WHILE beg > 0 LOOP temp_str := substring(string FROM beg FOR ss_length); IF string_to_search_for = temp_str THEN occur_number := occur_number + 1; IF occur_number = occur_index THEN RETURN beg; END IF; END IF; beg := beg - 1; END LOOP; RETURN 0; ELSE RETURN 0; END IF; END; $$ LANGUAGE plpgsql STRICT IMMUTABLE;