+ linkend="plpgsql-quote-tips">.
Here is an
Oracle PL/SQL> function:
-CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name IN varchar, v_version IN varchar)
+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
Let's go through this function and see the differences to
PL/pgSQL>:
-
-
PostgreSQL does not have named
- parameters. You have to explicitly alias them inside your
- function.
-
-
-
INOUT, for example, means that the
parameter will receive a value and return
another.
PostgreSQL> only has IN
- parameters.
+ parameters, and hence there is no specification of the parameter kind.
prototype (not the function body) becomes
RETURNS in
+ Also, IS> becomes AS>, and you need to
+ add a
LANGUAGE> clause because PL/pgSQL>
+ is not the only possible function language.
- In
PostgreSQL>, functions are created using
- single quotes as the delimiters of the function body, so you
- have to escape single quotes inside the function body.
+ 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 errors command does not exist in
+ The show errors command does not exist in
+
PostgreSQL>, and is not needed since errors are
+ reported automatically.
-CREATE OR REPLACE FUNCTION cs_fmt_browser_version(varchar, varchar)
+CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar,
+ v_version 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;
+ RETURN v_name;
END IF;
RETURN v_name || '/' || v_version;
END;
shows how to port a
- function that creates another function and how to handle to
+ function that creates another function and how to handle the
ensuing quoting problems.
SELECT statement and builds a large function
with the results in IF statements, for the
sake of efficiency. Notice particularly the differences in the
- cursor and the FOR loop,
+ cursor and the FOR loop.
SELECT * FROM cs_referrer_keys
ORDER BY try_order;
- a_output VARCHAR(4000);
+ func_cmd 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';
+ 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
- a_output := a_output || ' IF v_' || referrer_key.kind || ' LIKE ''' ||
-referrer_key.key_string || ''' THEN RETURN ''' || referrer_key.referrer_type ||
-'''; END IF;';
+ func_cmd := func_cmd ||
+ ' 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;
+ 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
-text AS $func$
+CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$
DECLARE
- referrer_keys RECORD; -- declare a generic record to be used in a FOR
- a_output TEXT;
+ referrer_key RECORD; -- declare a generic record to be used in a FOR
+ func_body text;
+ func_cmd text;
BEGIN
- a_output := 'CREATE FUNCTION cs_find_referrer_type(varchar, varchar, varchar)
- RETURNS varchar AS $innerfunc$
- DECLARE
- v_host ALIAS FOR $1;
- v_domain ALIAS FOR $2;
- v_url ALIAS FOR $3;
- BEGIN ';
-
- -- Notice how we scan through the results of a query in a FOR loop
- -- using the FOR <record> construct.
-
- 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; $innerfunc$ LANGUAGE plpgsql;';
-
- return a_output;
-END;
+ func_body := 'BEGIN' ;
+
+ -- Notice how we scan through the results of a query in a FOR loop
+ -- using the FOR <record> construct.
+
+ 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;
+ RETURN;
+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.
- The following
Oracle PL/SQL procedure is used to parse a URL and
- return several elements (host, path, and query).
-
PL/pgSQL functions can return only one value. In
-
PostgreSQL>, one way to work around this is to split the procedure
- in three different functions: one to return the host, another for
- the path, and another for the query.
+ The following
Oracle PL/SQL procedure is used
+ to parse a URL and return several elements (host, path, and query).
+ In
PostgreSQL>, functions can return only one value.
+ One way to work around this is to make the return value a composite
+ type (row type).
- Here is how the
PL/pgSQL> function that returns
- the host part could look like:
-
+ Here is a possible translation into
PL/pgSQL>:
-CREATE OR REPLACE FUNCTION cs_parse_url_host(varchar) RETURNS varchar AS $$
+CREATE TYPE cs_parse_url_result AS (
+ v_host VARCHAR,
+ v_path VARCHAR,
+ v_query VARCHAR
+);
+
+CREATE OR REPLACE FUNCTION cs_parse_url(v_url VARCHAR)
+RETURNS cs_parse_url_result 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;
+ res cs_parse_url_result;
+ a_pos1 INTEGER;
+ a_pos2 INTEGER;
+BEGIN
+ res.v_host := NULL;
+ res.v_path := NULL;
+ res.v_query := NULL;
a_pos1 := instr(v_url, '//');
- IF a_pos1 = 0 THEN
- RETURN ''; -- Return a blank
- END IF;
+ IF a_pos1 = 0 THEN
+ RETURN res;
+ END IF;
+ a_pos2 := instr(v_url, '/', a_pos1 + 2);
+ IF a_pos2 = 0 THEN
+ res.v_host := substr(v_url, a_pos1 + 2);
+ res.v_path := '/';
+ RETURN res;
+ 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;
+ res.v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
+ a_pos1 := instr(v_url, '?', a_pos2 + 1);
- v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2 );
- RETURN v_host;
-END;
+ IF a_pos1 = 0 THEN
+ res.v_path := substr(v_url, a_pos2);
+ RETURN res;
+ END IF;
+
+ res.v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
+ res.v_query := substr(v_url, a_pos1 + 1);
+ RETURN res;
+END;
$$ LANGUAGE plpgsql;
+
+
+ This function could be used like this:
+SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');
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
+ EXCEPTION
+ WHEN dup_val_on_index THEN NULL; -- don't worry if it already exists
END;
COMMIT;
END;
- Procedures like this can
be easily converted into
PostgreSQL>
- functions returning an integer. This procedure in
+ 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:
- If you do a
LOCK TABLE in
PL/pgSQL>, the lock
- will not be released until the calling transaction is finished.
+ If you do a
LOCK TABLE in
PL/pgSQL>,
+ the lock will not be released until the calling transaction is
+ finished.
- You also cannot have transactions in
PL/pgSQL functions. The
- entire function (and other functions called from therein) is
- executed in one transaction and
PostgreSQL> rolls back the transaction if
- something goes wrong.
-
-
-
-
- The exception when would have to be replaced by an
- IF statement.
+ You cannot issue COMMIT> in a
+
PL/pgSQL function. The function is
+ running within some outer transaction and so COMMIT>
+ would imply terminating the function's execution. However, in
+ this particular case it is not necessary anyway, because the lock
+ obtained by the LOCK TABLE will 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(integer) RETURNS integer AS $$
+CREATE OR REPLACE FUNCTION cs_create_job(v_job_id integer) RETURNS void 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.';
+ 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.';
- END IF;
+ BEGIN
+ INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
+ EXCEPTION
+ WHEN unique_violation THEN
+ -- don't worry if it already exists
+ END;
- RETURN 0;
+ RETURN;
END;
$$ LANGUAGE plpgsql;
- Notice how you can raise notices (or errors) in
PL/pgSQL>.
+ The syntax of RAISE> is considerably different from
+ Oracle's similar statement.
+
+
+
+ The exception names supported by
PL/pgSQL> are
+ different from Oracle's. The set of built-in exception names
+ is much larger (see ).
+
+ 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.
PostgreSQL> gives you two function creation
- modifiers to optimize execution: the volatility (whether the
+ modifiers to optimize execution: volatility>
(whether the
function always returns the same result when given the same
- arguments) and the strictness
(whether the
- function returns null if any argument is null). Consult the description of
- <command>CREATE FUNCTION for details.
+ arguments) and strictness
(whether the
+ function returns null if any argument is null). Consult the
+ <xref linkend="sql-createfunction"> reference page for details.
- To make use of these optimization attributes, your
- CREATE FUNCTION statement could look something
+ When making use of these optimization attributes, your
+ CREATE FUNCTION statement might look something
like this:
Appendix
- This section contains the code for an Oracle-compatible
- instr function that you can use to simplify
+ This section contains the code for a set of Oracle-compatible
+ instr functions that you can use to simplify
your porting efforts.
pos:= instr($1, $2, 1);
RETURN pos;
END;
-$$ LANGUAGE plpgsql;
+$$ LANGUAGE plpgsql STRICT IMMUTABLE;
-CREATE FUNCTION instr(varchar, varchar, integer) RETURNS integer AS $$
+CREATE FUNCTION instr(string varchar, string_to_search varchar, beg_index integer)
+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;
RETURN 0;
END IF;
END;
-$$ LANGUAGE plpgsql;
+$$ LANGUAGE plpgsql STRICT IMMUTABLE;
-CREATE FUNCTION instr(varchar, varchar, integer, integer) RETURNS integer AS $$
+CREATE FUNCTION instr(string varchar, string_to_search varchar,
+ beg_index integer, occur_index 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_number integer NOT NULL DEFAULT 0;
temp_str varchar;
RETURN 0;
END IF;
END;
-$$ LANGUAGE plpgsql;
+$$ LANGUAGE plpgsql STRICT IMMUTABLE;