Documentation PostgreSQL 7.4.29 | ||||
---|---|---|---|---|
Précédent | Arrière rapide | Chapitre 37. PL/pgSQL - SQL Procedural Language | Avance rapide | Suivant |
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 assignations, boucles, conditionnelles sont similaires. Les principales différences que vous devez garder à l'esprit quand vous portez de PL/SQL vers PL/pgSQL sont:
Il n'y a pas de valeurs par défaut pour les paramètres dans PostgreSQL.
Vous pouvez surcharger les fonctions dans PostgreSQL. C'est souvent utilisé pour contourner le manque de paramètres par défaut.
Pas besoin de curseurs dans PL/pgSQL, mettez juste la requête dans l'instruction FOR (voir Exemple 37-3).
Dans PostgreSQL vous avez besoin d'échapper les guillemets simples dans le corps des fonctions. Voir Section 37.2.1.
A la place des packages, utilisez des schémas pour organiser vos fonctions en groupes.
Exemple 37-2 montre comment porter une simple fonction de PL/SQL vers PL/pgSQL.
Exemple 37-2. 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 IN varchar, v_version IN varchar) RETURN varchar 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 :
PostgreSQL n'a pas de paramètres nommés. Vous devez les expliciter ou les aliaser dans votre fonction.
Oracle peut avoir des paramètres IN, OUT, et INOUT passées aux fonctions. INOUT, par exemple, signifie que le paramètre recevra une valeur et en renverra une autre. PostgreSQL n'a que les paramètres IN.
Le mot clé RETURN dans le prototype de la fonction (pas dans le corps de la fonction) devient RETURNS dans PostgreSQL.
Dans PostgreSQL, les fonctions sont créées en utilisant des guillemets simples comme délimiteurs du corps de la fonction, vous devez donc échapper les guillemets simples dans le corps de la fonction.
La commande /show errors n'existe pas dans PostgreSQL.
Voici de quoi aurait l'air cette fonction portée sous PostgreSQL :
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(varchar, varchar) RETURNS varchar AS ' DECLARE v_name ALIAS FOR $1; v_version ALIAS FOR $2; BEGIN IF v_version IS NULL THEN return v_name; END IF; RETURN v_name || ''/'' || v_version; END; ' LANGUAGE plpgsql;
L'Exemple 37-3 montre comment porter une fonction qui crée une autre fonction et comment gérer les problèmes de guillemets résultants.
Exemple 37-3. 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é. Notez particulièrement les différences dans le curseur et la boucle FOR.
Voici la version Oracle :
CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS CURSOR referrer_keys IS SELECT * FROM cs_referrer_keys ORDER BY try_order; a_output VARCHAR(4000); BEGIN a_output := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR, v_domain IN VARCHAR, v_url IN VARCHAR) RETURN VARCHAR IS BEGIN'; FOR referrer_key IN referrer_keys LOOP a_output := a_output || ' IF v_' || referrer_key.kind || ' LIKE ''' || referrer_key.key_string || ''' THEN RETURN ''' || referrer_key.referrer_type || '''; END IF;'; END LOOP; a_output := a_output || ' RETURN NULL; END;'; EXECUTE IMMEDIATE a_output; END; / show errors;
Voici comment la fonction serait dans PostgreSQL :
CREATE FUNCTION cs_update_referrer_type_proc() RETURNS integer AS ' DECLARE referrer_keys RECORD; -- Declare a generic record to be used in a FOR a_output varchar(4000); BEGIN a_output := ''CREATE FUNCTION cs_find_referrer_type(varchar, varchar, varchar) RETURNS varchar AS '''' DECLARE v_host ALIAS FOR $1; v_domain ALIAS FOR $2; v_url ALIAS FOR $3; BEGIN ''; -- Remarquez comment nous parcourons les résultats d'une requête dans une boucle FOR -- en utilisant la construction FOR <record>. FOR referrer_keys IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP a_output := a_output || '' IF v_'' || referrer_keys.kind || '' LIKE '''''''''' || referrer_keys.key_string || '''''''''' THEN RETURN '''''' || referrer_keys.referrer_type || ''''''; END IF;''; END LOOP; a_output := a_output || '' RETURN NULL; END; '''' LANGUAGE plpgsql;''; -- EXECUTE fonctionnera parce que nous ne substituons aucune variable. -- Autrement cela échouerait. Regardez PERFORM pour d'autres façons d'exécuter une fonction. EXECUTE a_output; END; ' LANGUAGE plpgsql;
Exemple 37-4 montre comment porter une fonction ayant des
paramètres OUT et effectuant des manipulations de chaînes.
PostgreSQL n'a pas de fonction instr
, mais
vous pouvez contourner cela en utilisant une combinaison d'autres fonctions. Dans Section 37.11.3 il y a une implémentation
PL/pgSQL d'instr
que vous pouvez
utiliser pour faciliter votre portage.
Exemple 37-4. 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ées 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. Dans PostgreSQL, un moyen de contourner cela est de décomposer la procédure en trois fonctions différentes : la première pour renvoyer l'hôte, une autre pour le chemin et la denière pour la requête.
Voici la version Oracle :
CREATE OR REPLACE PROCEDURE cs_parse_url( v_url IN VARCHAR, v_host OUT VARCHAR, -- Celle-ci sera passée en retour v_path OUT VARCHAR, -- Celle-là aussi v_query OUT VARCHAR) -- 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 à quoi ressemble la fonction PL/pgSQL renvoyant la partie hôte :
CREATE OR REPLACE FUNCTION cs_parse_url_host(varchar) RETURNS varchar AS ' DECLARE v_url ALIAS FOR $1; v_host varchar; v_path varchar; a_pos1 integer; a_pos2 integer; a_pos3 integer; BEGIN v_host := NULL; a_pos1 := instr(v_url, ''//''); IF a_pos1 = 0 THEN RETURN ''''; -- Renvoie un blanc 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 v_host; END IF; v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2 ); RETURN v_host; END; ' LANGUAGE plpgsql;
L'Exemple 37-5 montre comment porter une procédure qui utilise de nombreuses fonctionnalités spécifiques à Oracle.
Exemple 37-5. 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; PRAGMA AUTONOMOUS_TRANSACTION;(1) BEGIN LOCK TABLE cs_jobs IN EXCLUSIVE MODE;(2) 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(3) 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à(4) END; COMMIT; END; / show errors
Les procédures comme celles-ci peuvent être aisément converties en fonctions PostgreSQL renvoyant un integer. Cette procédure en particulier est intéressante parce qu'elle peut nous apprendre diverses choses :
Voici comment nous pourrions porter cette procédure vers PL/pgSQL :
CREATE OR REPLACE FUNCTION cs_create_job(integer) RETURNS integer AS ' DECLARE v_job_id ALIAS FOR $1; a_running_job_count integer; a_num 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 RAISE EXCEPTION ''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); SELECT count(*) INTO a_num FROM cs_jobs WHERE job_id=v_job_id; IF NOT FOUND THEN -- If nothing was returned in the last query -- This job is not in the table so lets insert it. INSERT INTO cs_jobs(job_id, start_stamp) VALUES (v_job_id, current_timestamp); RETURN 1; ELSE RAISE NOTICE ''Job already running.'';(1) END IF; RETURN 0; END; ' LANGUAGE plpgsql;
Cette section explique quelques autres choses à surveiller quand on effectue un portage de fonctions PL/SQL Oracle vers PostgreSQL.
La version PL/pgSQL d'EXECUTE fonctionne de façon
similaire à la version PL/SQL, mais vous devez vous rappeler
d'utiliser quote_literal(text)
et
quote_string(text)
comme décrit dans Section 37.6.4. Les constructions de type
EXECUTE ''SELECT * FROM $1''; ne fonctionneront pas à
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 description de CREATE FUNCTION pour les détails.
Pour faire usage de ces attributs d'optimisation, votre instruction CREATE FUNCTION devrait ressembler a ceci:
CREATE FUNCTION foo(...) RETURNS integer AS ' ... ' LANGUAGE plpgsql STRICT IMMUTABLE;
Cette section contient le code d'une fonction instr
compatible Oracle que vous pouvez utiliser pour simplifier vos efforts de portage.
-- -- fonctions instr qui reproduisent la contrepartie 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. Si m n'est pas fourni -- suppose 1 (la recherche commence au premier caractère). -- CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS ' DECLARE pos integer; BEGIN pos:= instr($1, $2, 1); RETURN pos; END; ' LANGUAGE plpgsql; CREATE FUNCTION instr(varchar, varchar, varchar) RETURNS integer AS ' DECLARE string ALIAS FOR $1; string_to_search ALIAS FOR $2; beg_index ALIAS FOR $3; 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 IN temp_str); IF pos = 0 THEN RETURN 0; ELSE RETURN pos + beg_index - 1; END IF; ELSE ss_length := char_length(string_to_search); length := char_length(string); beg := length + beg_index - ss_length + 2; WHILE beg > 0 LOOP temp_str := substring(string FROM beg FOR ss_length); pos := position(string_to_search IN temp_str); IF pos > 0 THEN RETURN beg; END IF; beg := beg - 1; END LOOP; RETURN 0; END IF; END; ' LANGUAGE plpgsql; CREATE FUNCTION instr(varchar, varchar, integer, integer) RETURNS integer AS ' DECLARE string ALIAS FOR $1; string_to_search ALIAS FOR $2; beg_index ALIAS FOR $3; occur_index ALIAS FOR $4; pos integer NOT NULL DEFAULT 0; occur_nombre integer NOT NULL DEFAULT 0; occur_nombre integer NOT NULL DEFAULT 0; temp_str varchar; beg integer; i integer; length integer; ss_length integer; BEGIN IF beg_index > 0 THEN beg := beg_index; temp_str := substring(string FROM beg_index); FOR i IN 1..occur_index LOOP pos := position(string_to_search IN temp_str); IF i = 1 THEN beg := beg + pos - 1; ELSE beg := beg + pos; END IF; temp_str := substring(string FROM beg + 1); END LOOP; IF pos = 0 THEN RETURN 0; ELSE RETURN beg; END IF; ELSE ss_length := char_length(string_to_search); length := char_length(string); beg := length + beg_index - ss_length + 2; occur_nombre integer NOT NULL DEFAULT 0; temp_str varchar; beg integer; i integer; length integer; ss_length integer; BEGIN IF beg_index > 0 THEN beg := beg_index; temp_str := substring(string FROM beg_index); FOR i IN 1..occur_index LOOP pos := position(string_to_search IN temp_str); IF i = 1 THEN beg := beg + pos - 1; ELSE beg := beg + pos; END IF; temp_str := substring(string FROM beg + 1); END LOOP; IF pos = 0 THEN RETURN 0; ELSE RETURN beg; END IF; ELSE ss_length := char_length(string_to_search); length := char_length(string); beg := length + beg_index - ss_length + 2; WHILE beg > 0 LOOP temp_str := substring(string FROM beg FOR ss_length); pos := position(string_to_search IN temp_str); IF pos > 0 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; END IF; END; ' LANGUAGE plpgsql;
Précédent | Sommaire | Suivant |
Procédures Déclencheur | Niveau supérieur | PL/Tcl - Langage de procédures Tcl |