| The PostgreSQL 9.0 Reference Manual - Volume 2 - Programming Guide
by The PostgreSQL Global Development Group Paperback (6"x9"), 478 pages ISBN 9781906966065 RRP £14.95 ($19.95) Sales of this book support the PostgreSQL project! Get a printed copy>>> |
9.12.1 Porting Examples
section 9.12.1 Porting Examples shows how to port a simple function from PL/SQL to PL/pgSQL.
Porting a Simple Function from PL/SQL to PL/pgSQL:
Here is an Oracle PL/SQL function:
CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name
varchar, v_version varchar)
RETURN varchar IS
BEGIN
IF v_version IS NULL THEN
RETURN v_name;
END IF;
RETURN v_name || '/' || v_version;
END;
/
show errors;
Let's go through this function and see the differences compared to PL/pgSQL:
-
The
RETURNkey word in the function prototype (not the function body) becomesRETURNSin PostgreSQL. Also,ISbecomesAS, and you need to add aLANGUAGEclause because PL/pgSQL is not the only possible function language. -
In PostgreSQL, the function body is considered
to be a string literal, so you need to use quote marks or dollar
quotes around it. This substitutes for the terminating
/in the Oracle approach. -
The
show errorscommand does not exist in PostgreSQL, and is not needed since errors are reported automatically.
This is how this function would look when ported to 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;
section 9.12.1 Porting Examples shows how to port a function that creates another function and how to handle the ensuing quoting problems.
Porting a Function that Creates Another Function from PL/SQL to PL/pgSQL:
The following procedure grabs rows from a
SELECT statement and builds a large function
with the results in IF statements, for the
sake of efficiency.
This is the Oracle version:
CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
CURSOR referrer_keys 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 VARCHAR, v_domain IN VARCHAR,
v_url IN VARCHAR) RETURN VARCHAR 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;
Here is how this function would end up in PostgreSQL:
CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc()
RETURNS void AS $func$
DECLARE
referrer_keys CURSOR IS
SELECT * FROM cs_referrer_keys
ORDER BY try_order;
func_body text;
func_cmd text;
BEGIN
func_body := 'BEGIN';
FOR referrer_key IN referrer_keys 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;
Notice how the body of the function is built separately and passed
through quote_literal to double any quote marks in it. This
technique is needed because we cannot safely use dollar quoting for
defining the new function: we do not know for sure what strings will
be interpolated from the referrer_key.key_string field.
(We are assuming here that referrer_key.kind can be
trusted to always be host, domain, or
url, but referrer_key.key_string might be
anything, in particular it might contain dollar signs.) This function
is actually an improvement on the Oracle original, because it will
not generate broken code when referrer_key.key_string or
referrer_key.referrer_type contain quote marks.
section 9.12.1 Porting Examples shows how to port a function
with OUT parameters and string manipulation.
PostgreSQL does not have a built-in
instr function, but you can create one
using a combination of other
functions.
In section 9.12.3 Appendix there is a
PL/pgSQL implementation of
instr that you can use to make your porting
easier.
Porting a Procedure With String Manipulation and OUT Parameters from PL/SQL to PL/pgSQL:
The following Oracle PL/SQL procedure is used to parse a URL and return several elements (host, path, and query).
This is the Oracle version:
CREATE OR REPLACE PROCEDURE 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
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;
Here is a possible translation into 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;
This function could be used like this:
SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');
section 9.12.1 Porting Examples shows how to port a procedure that uses numerous features that are specific to Oracle.
Porting a Procedure from PL/SQL to PL/pgSQL:
The Oracle version:
CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS
a_running_job_count INTEGER;
PRAGMA AUTONOMOUS_TRANSACTION; (note 1)
BEGIN
LOCK TABLE cs_jobs IN EXCLUSIVE MODE; (note 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 (note 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; -- don't worry if it
already exists
END;
COMMIT;
END;
/
show errors
Procedures like this can easily be converted into PostgreSQL
functions returning void. This procedure in
particular is interesting because it can teach us some things:
- Note 1:
-
There is no
PRAGMAstatement in PostgreSQL. - Note 2:
-
If you do a
LOCK TABLEin PL/pgSQL, the lock will not be released until the calling transaction is finished. - Note 3:
-
You cannot issue
COMMITin a PL/pgSQL function. The function is running within some outer transaction and soCOMMITwould imply terminating the function's execution. However, in this particular case it is not necessary anyway, because the lock obtained by theLOCK TABLEwill be released when we raise an error.
This is how we could port this procedure to PL/pgSQL:
CREATE OR REPLACE FUNCTION cs_create_job(v_job_id integer)
RETURNS void 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
RAISE EXCEPTION 'Unable to create a new job: a job is
currently running'; (note 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 (note 2)
-- don't worry if it already exists
END;
END;
$$ LANGUAGE plpgsql;
- Note 1:
-
The syntax of
RAISEis considerably different from Oracle's statement, although the basic caseRAISEexception_name works similarly. - Note 2:
- The exception names supported by PL/pgSQL are different from Oracle's. The set of built-in exception names is much larger (see Volume 1A: A PostgreSQL Error Codes). There is not currently a way to declare user-defined exception names, although you can throw user-chosen SQLSTATE values instead.
The main functional difference between this procedure and the
Oracle equivalent is that the exclusive lock on the cs_jobs
table will be held until the calling transaction completes. Also, if
the caller later aborts (for example due to an error), the effects of
this procedure will be rolled back.
| ISBN 9781906966065 | The PostgreSQL 9.0 Reference Manual - Volume 2 - Programming Guide | See the print edition |