Commit 11fd9157 authored by Peter Eisentraut's avatar Peter Eisentraut

Repair.

parent 406183fb
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/Attic/plsql.sgml,v 2.23 2001/03/17 01:53:22 thomas Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/plsql.sgml,v 2.24 2001/03/17 18:08:14 petere Exp $
--> -->
<chapter id="plpgsql"> <chapter id="plpgsql">
<title>PL/pgSQL - <acronym>SQL<acronym> Procedural Language</title> <title>PL/pgSQL - <acronym>SQL</acronym> Procedural Language</title>
<para> <para>
PL/pgSQL is a loadable procedural language for the PL/pgSQL is a loadable procedural language for the
...@@ -12,8 +12,8 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/plsql.sgml,v 2.23 2001/03/17 01:53:22 ...@@ -12,8 +12,8 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/plsql.sgml,v 2.23 2001/03/17 01:53:22
<para> <para>
This package was originally written by Jan Wieck. This This package was originally written by Jan Wieck. This
documentation was re-organized and in part written documentation was in part written
by Roberto Mello (rmello@fslc.usu.edu). by Roberto Mello (<email>rmello@fslc.usu.edu</email>).
</para> </para>
<sect1 id="plpgsql-overview"> <sect1 id="plpgsql-overview">
...@@ -80,20 +80,23 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/plsql.sgml,v 2.23 2001/03/17 01:53:22 ...@@ -80,20 +80,23 @@ $Header: /cvsroot/pgsql/doc/src/sgml/Attic/plsql.sgml,v 2.23 2001/03/17 01:53:22
This means that you have to be careful about your user-defined This means that you have to be careful about your user-defined
functions. For example: functions. For example:
<programlisting> <programlisting>
CREATE FUNCTION populate() RETURNS INTEGER AS ' CREATE FUNCTION populate() RETURNS INTEGER AS '
DECLARE DECLARE
-- Declarations -- Declarations
BEGIN BEGIN
PERFORM my_function(); PERFORM my_function();
END; END;
' language 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
If you CREATE the above function, it will reference the ID for If you create the above function, it will reference the OID for
my_function() in its bytecode. Later, if you DROP and re-CREATE <function>my_function()</function> in its bytecode. Later, if you
my_function(), populate() will not be able to find my_function() drop and re-create <function>my_function()</function>, then
anymore. You'll have to re-CREATE populate(). <function>populate()</function> will not be able to find
<function>my_function()</function> anymore. You would then have to
re-create <function>populate()</function>.
</para> </para>
<para> <para>
Because PL/pgSQL saves execution plans in this way, queries that appear Because PL/pgSQL saves execution plans in this way, queries that appear
directly in a PL/pgSQL function must refer to the same tables and fields directly in a PL/pgSQL function must refer to the same tables and fields
...@@ -116,23 +119,26 @@ END; ...@@ -116,23 +119,26 @@ END;
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
<xref linkend="plpgsql-advantages-performance">Better performance</xref> Better performance (see <xref linkend="plpgsql-advantages-performance">)
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<xref linkend="plpgsql-advantages-sqlsupport">SQL Support</xref> SQL support (see <xref linkend="plpgsql-advantages-sqlsupport">)
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<xref linkend="plpgsql-advantages-portability">Portability</xref> Portability (see <xref linkend="plpgsql-advantages-portability">)
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<sect3 id="plpgsql-advantages-performance"> <sect3 id="plpgsql-advantages-performance">
<title>Better Performance</title> <title>Better Performance</title>
<para> <para>
<acronym>SQL</acronym> is the language PostgreSQL (and <acronym>SQL</acronym> is the language PostgreSQL (and
most other Relational Databases) use as query most other Relational Databases) use as query
...@@ -140,6 +146,7 @@ END; ...@@ -140,6 +146,7 @@ END;
<acronym>SQL</acronym> statement must be executed <acronym>SQL</acronym> statement must be executed
individually by the database server. individually by the database server.
</para> </para>
<para> <para>
That means that your client application must send each That means that your client application must send each
query to the database server, wait for it to process it, query to the database server, wait for it to process it,
...@@ -149,6 +156,7 @@ END; ...@@ -149,6 +156,7 @@ END;
overhead if your client is on a different machine than overhead if your client is on a different machine than
the database server. the database server.
</para> </para>
<para> <para>
With PL/pgSQL you can group a block of computation and a With PL/pgSQL you can group a block of computation and a
series of queries <emphasis>inside</emphasis> the series of queries <emphasis>inside</emphasis> the
...@@ -159,8 +167,10 @@ END; ...@@ -159,8 +167,10 @@ END;
considerable performance increase by using PL/pgSQL. considerable performance increase by using PL/pgSQL.
</para> </para>
</sect3> </sect3>
<sect3 id="plpgsql-advantages-sqlsupport"> <sect3 id="plpgsql-advantages-sqlsupport">
<title>SQL Support</title> <title>SQL Support</title>
<para> <para>
PL/pgSQL adds the power of a procedural language to the PL/pgSQL adds the power of a procedural language to the
flexibility and ease of <acronym>SQL</acronym>. With flexibility and ease of <acronym>SQL</acronym>. With
...@@ -168,8 +178,10 @@ END; ...@@ -168,8 +178,10 @@ END;
and functions of SQL. and functions of SQL.
</para> </para>
</sect3> </sect3>
<sect3 id="plpgsql-advantages-portability"> <sect3 id="plpgsql-advantages-portability">
<title>Portability</title> <title>Portability</title>
<para> <para>
Because PL/pgSQL functions run inside PostgreSQL, these Because PL/pgSQL functions run inside PostgreSQL, these
functions will run on any platform where PostgreSQL functions will run on any platform where PostgreSQL
...@@ -186,12 +198,14 @@ END; ...@@ -186,12 +198,14 @@ END;
if you have developed in other database procedural languages, if you have developed in other database procedural languages,
such as Oracle's PL/SQL. Two good ways of developing in such as Oracle's PL/SQL. Two good ways of developing in
PL/pgSQL are: PL/pgSQL are:
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
Using a text editor and reloading the file with <command>psql</command> Using a text editor and reloading the file with <command>psql</command>
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
Using PostgreSQL's GUI Tool: pgaccess Using PostgreSQL's GUI Tool: pgaccess
...@@ -199,6 +213,7 @@ END; ...@@ -199,6 +213,7 @@ END;
</listitem> </listitem>
</itemizedlist> </itemizedlist>
</para> </para>
<para> <para>
One good way to develop in PL/pgSQL is to simply use the text One good way to develop in PL/pgSQL is to simply use the text
editor of your choice to create your functions, and in another editor of your choice to create your functions, and in another
...@@ -208,26 +223,26 @@ END; ...@@ -208,26 +223,26 @@ END;
always <command>DROP</command> your function before creating it. That way always <command>DROP</command> your function before creating it. That way
when you reload the file, it'll drop your functions and then when you reload the file, it'll drop your functions and then
re-create them. For example: re-create them. For example:
</para> <programlisting>
<para>
<programlisting>
drop function testfunc(integer); drop function testfunc(integer);
create function testfunc(integer) return integer as ' create function testfunc(integer) return integer as '
.... ....
end; end;
' language 'plpgsql'; ' language 'plpgsql';
</programlisting> </programlisting>
</para>
<para>
When you load the file for the first time, When you load the file for the first time,
<productname>PostgreSQL</> will raise a warning saying this <productname>PostgreSQL</> will raise a warning saying this
function doesn't exist and go on to create it. To load an SQL function doesn't exist and go on to create it. To load an SQL
file (filename.sql) into a database named "dbname", use the command: file (filename.sql) into a database named "dbname", use the command:
</para> <programlisting>
<para>
<programlisting>
psql -f filename.sql dbname psql -f filename.sql dbname
</programlisting> </programlisting>
</para>
<para>
Another good way to develop in PL/pgSQL is using Another good way to develop in PL/pgSQL is using
<productname>PostgreSQL</>'s GUI tool: pgaccess. It does some <productname>PostgreSQL</>'s GUI tool: pgaccess. It does some
nice things for you, like escaping single-quotes, and making nice things for you, like escaping single-quotes, and making
...@@ -247,31 +262,31 @@ psql -f filename.sql dbname ...@@ -247,31 +262,31 @@ psql -f filename.sql dbname
<title>Structure of PL/pgSQL</title> <title>Structure of PL/pgSQL</title>
<para> <para>
PL/pgSQL is a <emphasis>block structured</emphasis>, case PL/pgSQL is a <emphasis>block structured</emphasis> language. All
insensitive language. All keywords and identifiers can be keywords and identifiers can be used in mixed upper and
used in mixed upper- and lower-case. A block is defined as: lower-case. A block is defined as:
</para>
<synopsis> <synopsis>
<optional>&lt;&lt;label&gt;&gt;</optional> <optional>&lt;&lt;label&gt;&gt;</optional>
<optional>DECLARE <optional>DECLARE
<replaceable>declarations</replaceable></optional> <replaceable>declarations</replaceable></optional>
BEGIN BEGIN
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END; END;
</synopsis> </synopsis>
</para>
<para> <para>
There can be any number of sub-blocks in the statement section There can be any number of sub-blocks in the statement section
of a block. Sub-blocks can be used to hide variables from outside a of a block. Sub-blocks can be used to hide variables from outside a
block of statements. block of statements.
</para> </para>
<para> <para>
The variables declared in the declarations section preceding a The variables declared in the declarations section preceding a
block are initialized to their default values every time the block are initialized to their default values every time the
block is entered, not only once per function call. For example: block is entered, not only once per function call. For example:
</para> <programlisting>
<programlisting>
CREATE FUNCTION somefunc() RETURNS INTEGER AS ' CREATE FUNCTION somefunc() RETURNS INTEGER AS '
DECLARE DECLARE
quantity INTEGER := 30; quantity INTEGER := 30;
...@@ -289,8 +304,9 @@ BEGIN ...@@ -289,8 +304,9 @@ BEGIN
RAISE NOTICE ''Quantity here is %'',quantity; -- Quantity here is 50 RAISE NOTICE ''Quantity here is %'',quantity; -- Quantity here is 50
END; END;
' language 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</para>
<para> <para>
It is important not to confuse the use of BEGIN/END for It is important not to confuse the use of BEGIN/END for
...@@ -326,30 +342,33 @@ END; ...@@ -326,30 +342,33 @@ END;
The exception being the loop variable of a FOR loop iterating over a range The exception being the loop variable of a FOR loop iterating over a range
of integer values. of integer values.
</para> </para>
<para> <para>
PL/pgSQL variables can have any SQL datatype, such as PL/pgSQL variables can have any SQL datatype, such as
<type>INTEGER</type>, <type>VARCHAR</type> and <type>INTEGER</type>, <type>VARCHAR</type> and
<type>CHAR</type>. All variables have as default value the <type>CHAR</type>. All variables have as default value the
<acronym>SQL</acronym> NULL value. <acronym>SQL</acronym> NULL value.
</para> </para>
<para> <para>
Here are some examples of variable declarations: Here are some examples of variable declarations:
</para> <programlisting>
<programlisting>
user_id INTEGER; user_id INTEGER;
quantity NUMBER(5); quantity NUMBER(5);
url VARCHAR; url VARCHAR;
</programlisting> </programlisting>
</para>
<sect3 id="plpgsql-description-default-vars"> <sect3 id="plpgsql-description-default-vars">
<title>Constants and Variables With Default Values</title> <title>Constants and Variables With Default Values</title>
<para> <para>
The declarations have the following syntax: The declarations have the following syntax:
</para> <synopsis>
<synopsis>
<replaceable>name</replaceable> <optional> CONSTANT </optional> <replaceable>type</replaceable> <optional> NOT NULL </optional> <optional> { DEFAULT | := } <replaceable>value</replaceable> </optional>; <replaceable>name</replaceable> <optional> CONSTANT </optional> <replaceable>type</replaceable> <optional> NOT NULL </optional> <optional> { DEFAULT | := } <replaceable>value</replaceable> </optional>;
</synopsis> </synopsis>
</para>
<para> <para>
The value of variables declared as CONSTANT cannot be changed. If NOT NULL The value of variables declared as CONSTANT cannot be changed. If NOT NULL
is specified, an assignment of a NULL value results in a runtime is specified, an assignment of a NULL value results in a runtime
...@@ -357,6 +376,7 @@ url VARCHAR; ...@@ -357,6 +376,7 @@ url VARCHAR;
<acronym>SQL</acronym> NULL value, all variables declared as NOT NULL <acronym>SQL</acronym> NULL value, all variables declared as NOT NULL
must also have a default value specified. must also have a default value specified.
</para> </para>
<para> <para>
The default value is evaluated every time the function is called. So The default value is evaluated every time the function is called. So
assigning '<literal>now</literal>' to a variable of type assigning '<literal>now</literal>' to a variable of type
...@@ -364,14 +384,15 @@ url VARCHAR; ...@@ -364,14 +384,15 @@ url VARCHAR;
time of the actual function call, not when the function was time of the actual function call, not when the function was
precompiled into its bytecode. precompiled into its bytecode.
</para> </para>
<para> <para>
Examples: Examples:
</para> <programlisting>
<programlisting>
quantity INTEGER := 32; quantity INTEGER := 32;
url varchar := ''http://mysite.com''; url varchar := ''http://mysite.com'';
user_id CONSTANT INTEGER := 10; user_id CONSTANT INTEGER := 10;
</programlisting> </programlisting>
</para>
</sect3> </sect3>
<sect3 id="plpgsql-description-passed-vars"> <sect3 id="plpgsql-description-passed-vars">
...@@ -381,15 +402,14 @@ user_id CONSTANT INTEGER := 10; ...@@ -381,15 +402,14 @@ user_id CONSTANT INTEGER := 10;
Variables passed to functions are named with the identifiers Variables passed to functions are named with the identifiers
<literal>$1</literal>, <literal>$2</literal>, <literal>$1</literal>, <literal>$2</literal>,
etc. (maximum is 16). Some examples: etc. (maximum is 16). Some examples:
</para> <programlisting>
<programlisting>
CREATE FUNCTION sales_tax(REAL) RETURNS REAL AS ' CREATE FUNCTION sales_tax(REAL) RETURNS REAL AS '
DECLARE DECLARE
subtotal ALIAS FOR $1; subtotal ALIAS FOR $1;
BEGIN BEGIN
return subtotal * 0.06; return subtotal * 0.06;
END; END;
' language 'plpgsql'; ' LANGUAGE 'plpgsql';
CREATE FUNCTION instr(VARCHAR,INTEGER) RETURNS INTEGER AS ' CREATE FUNCTION instr(VARCHAR,INTEGER) RETURNS INTEGER AS '
...@@ -399,8 +419,9 @@ DECLARE ...@@ -399,8 +419,9 @@ DECLARE
BEGIN BEGIN
-- Some computations here -- Some computations here
END; END;
' language 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</para>
</sect3> </sect3>
<sect3 id="plpgsql-description-attributes"> <sect3 id="plpgsql-description-attributes">
...@@ -427,10 +448,11 @@ END; ...@@ -427,10 +448,11 @@ END;
named <type>user_id</type> in your named <type>user_id</type> in your
<type>users</type> table. To declare a variable with <type>users</type> table. To declare a variable with
the same datatype as users you do: the same datatype as users you do:
</para> <programlisting>
<programlisting>
user_id users.user_id%TYPE; user_id users.user_id%TYPE;
</programlisting> </programlisting>
</para>
<para> <para>
By using <type>%TYPE</type> you don't need to know By using <type>%TYPE</type> you don't need to know
the datatype of the structure you are referencing, the datatype of the structure you are referencing,
...@@ -449,22 +471,23 @@ user_id users.user_id%TYPE; ...@@ -449,22 +471,23 @@ user_id users.user_id%TYPE;
</term> </term>
<listitem> <listitem>
<para> <para>
Declares a row with the structure of the given table. <replaceable>table</replaceable> must be Declares a row with the structure of the given
an existing table or view name of the database. The fields of the row table. <replaceable>table</replaceable> must be an existing
are accessed in the dot notation. Parameters to a function can table or view name of the database. The fields of the row are
be composite types (complete table rows). In that case, the accessed in the dot notation. Parameters to a function can be
corresponding identifier $n will be a rowtype, but it composite types (complete table rows). In that case, the
must be aliased using the ALIAS command described above. corresponding identifier $n will be a rowtype, but it must be
aliased using the ALIAS command described above.
</para> </para>
<para> <para>
Only the user Only the user attributes of a table row are accessible in the
attributes of a table row are accessible in the row, no OID or other row, no OID or other system attributes (because the row could
system attributes (because the row could be from a view). be from a view). The fields of the rowtype inherit the
The fields of the rowtype inherit the table's field sizes table's field sizes or precision for <type>char()</type>
or precision for <type>char()</type> etc. data types. etc. data types.
</para> </para>
<programlisting> <programlisting>
DECLARE DECLARE
users_rec users%ROWTYPE; users_rec users%ROWTYPE;
user_id users%TYPE; user_id users%TYPE;
...@@ -493,8 +516,8 @@ create function cs_refresh_one_mv(integer) returns integer as ' ...@@ -493,8 +516,8 @@ create function cs_refresh_one_mv(integer) returns integer as '
return 1; return 1;
end; end;
' language 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
...@@ -504,20 +527,22 @@ end; ...@@ -504,20 +527,22 @@ end;
<title> <title>
RENAME RENAME
</title> </title>
<para> <para>
Using RENAME you can change the name of a variable, record Using RENAME you can change the name of a variable, record
or row. This is useful if NEW or OLD should be referenced or row. This is useful if NEW or OLD should be referenced
by another name inside a trigger procedure. by another name inside a trigger procedure.
</para> </para>
<para> <para>
Syntax and examples: Syntax and examples:
</para> <programlisting>
<programlisting>
RENAME <replaceable>oldname</replaceable> TO <replaceable>newname</replaceable>; RENAME <replaceable>oldname</replaceable> TO <replaceable>newname</replaceable>;
RENAME id TO user_id; RENAME id TO user_id;
RENAME this_var TO that_var; RENAME this_var TO that_var;
</programlisting> </programlisting>
</para>
</sect3> </sect3>
</sect2> </sect2>
...@@ -535,9 +560,9 @@ RENAME this_var TO that_var; ...@@ -535,9 +560,9 @@ RENAME this_var TO that_var;
it is impossible for the PL/pgSQL parser it is impossible for the PL/pgSQL parser
to identify real constant values other than the NULL keyword. All to identify real constant values other than the NULL keyword. All
expressions are evaluated internally by executing a query expressions are evaluated internally by executing a query
<synopsis> <synopsis>
SELECT <replaceable>expression</replaceable> SELECT <replaceable>expression</replaceable>
</synopsis> </synopsis>
using the <acronym>SPI</acronym> manager. In the expression, occurrences of variable using the <acronym>SPI</acronym> manager. In the expression, occurrences of variable
identifiers are substituted by parameters and the actual values from identifiers are substituted by parameters and the actual values from
the variables are passed to the executor in the parameter array. All the variables are passed to the executor in the parameter array. All
...@@ -545,13 +570,14 @@ SELECT <replaceable>expression</replaceable> ...@@ -545,13 +570,14 @@ SELECT <replaceable>expression</replaceable>
saved once. The only exception to this rule is an EXECUTE statement saved once. The only exception to this rule is an EXECUTE statement
if parsing of a query is needed each time it is encountered. if parsing of a query is needed each time it is encountered.
</para> </para>
<para> <para>
The type checking done by the <productname>Postgres</productname> The type checking done by the <productname>Postgres</productname>
main parser has some side main parser has some side
effects to the interpretation of constant values. In detail there effects to the interpretation of constant values. In detail there
is a difference between what these two functions do: is a difference between what these two functions do:
<programlisting> <programlisting>
CREATE FUNCTION logfunc1 (text) RETURNS timestamp AS ' CREATE FUNCTION logfunc1 (text) RETURNS timestamp AS '
DECLARE DECLARE
logtxt ALIAS FOR $1; logtxt ALIAS FOR $1;
...@@ -560,11 +586,11 @@ CREATE FUNCTION logfunc1 (text) RETURNS timestamp AS ' ...@@ -560,11 +586,11 @@ CREATE FUNCTION logfunc1 (text) RETURNS timestamp AS '
RETURN ''now''; RETURN ''now'';
END; END;
' LANGUAGE 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
and and
<programlisting> <programlisting>
CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
DECLARE DECLARE
logtxt ALIAS FOR $1; logtxt ALIAS FOR $1;
...@@ -575,7 +601,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' ...@@ -575,7 +601,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
RETURN curtime; RETURN curtime;
END; END;
' LANGUAGE 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
In the case of <function>logfunc1()</function>, the In the case of <function>logfunc1()</function>, the
<productname>Postgres</productname> main parser knows when <productname>Postgres</productname> main parser knows when
...@@ -588,6 +614,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' ...@@ -588,6 +614,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
backend. Needless to say that this isn't what the backend. Needless to say that this isn't what the
programmer wanted. programmer wanted.
</para> </para>
<para> <para>
In the case of <function>logfunc2()</function>, the In the case of <function>logfunc2()</function>, the
<productname>Postgres</productname> main parser does not know <productname>Postgres</productname> main parser does not know
...@@ -599,6 +626,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' ...@@ -599,6 +626,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
<function>text_out()</function> and <function>timestamp_in()</function> <function>text_out()</function> and <function>timestamp_in()</function>
functions for the conversion. functions for the conversion.
</para> </para>
<para> <para>
This type checking done by the <productname>Postgres</productname> main This type checking done by the <productname>Postgres</productname> main
parser got implemented after PL/pgSQL was nearly done. parser got implemented after PL/pgSQL was nearly done.
...@@ -608,6 +636,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' ...@@ -608,6 +636,7 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
variable in the above manner is currently the only way in PL/pgSQL to get variable in the above manner is currently the only way in PL/pgSQL to get
those values interpreted correctly. those values interpreted correctly.
</para> </para>
<para> <para>
If record fields are used in expressions or statements, the data types of If record fields are used in expressions or statements, the data types of
fields should not change between calls of one and the same expression. fields should not change between calls of one and the same expression.
...@@ -632,9 +661,9 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' ...@@ -632,9 +661,9 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
<para> <para>
An assignment of a value to a variable or row/record field is An assignment of a value to a variable or row/record field is
written as: written as:
<synopsis> <synopsis>
<replaceable>identifier</replaceable> := <replaceable>expression</replaceable>; <replaceable>identifier</replaceable> := <replaceable>expression</replaceable>;
</synopsis> </synopsis>
If the expressions result data type doesn't match the variables If the expressions result data type doesn't match the variables
data type, or the variable has a size/precision that is known data type, or the variable has a size/precision that is known
...@@ -644,10 +673,10 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' ...@@ -644,10 +673,10 @@ CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS '
result in runtime errors generated by the types input functions. result in runtime errors generated by the types input functions.
</para> </para>
<programlisting> <programlisting>
user_id := 20; user_id := 20;
tax := subtotal * 0.06; tax := subtotal * 0.06;
</programlisting> </programlisting>
</sect3> </sect3>
<sect3 id="plpgsql-statements-calling-other-funcs"> <sect3 id="plpgsql-statements-calling-other-funcs">
...@@ -659,26 +688,25 @@ tax := subtotal * 0.06; ...@@ -659,26 +688,25 @@ tax := subtotal * 0.06;
is to execute a SELECT query or doing an assignment (resulting is to execute a SELECT query or doing an assignment (resulting
in a PL/pgSQL internal SELECT). in a PL/pgSQL internal SELECT).
</para> </para>
<para> <para>
But there are cases where someone is not interested in the But there are cases where someone is not interested in the
function's result. In these cases, use the PERFORM function's result. In these cases, use the PERFORM
statement. statement.
</para> <synopsis>
<synopsis>
PERFORM <replaceable>query</replaceable> PERFORM <replaceable>query</replaceable>
</synopsis> </synopsis>
<para> This executes a <literal>SELECT <replaceable>query</replaceable></literal> over the
executes a <literal>SELECT <replaceable>query</replaceable></literal> over the
<acronym>SPI manager</acronym> and discards the result. Identifiers like local <acronym>SPI manager</acronym> and discards the result. Identifiers like local
variables are still substituted into parameters. variables are still substituted into parameters.
</para> </para>
<programlisting> <programlisting>
PERFORM create_mv(''cs_session_page_requests_mv'','' PERFORM create_mv(''cs_session_page_requests_mv'',''
select session_id, page_id, count(*) as n_hits, select session_id, page_id, count(*) as n_hits,
sum(dwell_time) as dwell_time, count(dwell_time) as dwell_count sum(dwell_time) as dwell_time, count(dwell_time) as dwell_count
from cs_fact_table from cs_fact_table
group by session_id, page_id ''); group by session_id, page_id '');
</programlisting> </programlisting>
</sect3> </sect3>
<sect3 id="plpgsql-statements-executing-dyn-queries"> <sect3 id="plpgsql-statements-executing-dyn-queries">
...@@ -690,14 +718,16 @@ PERFORM create_mv(''cs_session_page_requests_mv'','' ...@@ -690,14 +718,16 @@ PERFORM create_mv(''cs_session_page_requests_mv'',''
generate other functions. PL/pgSQL provides the EXECUTE generate other functions. PL/pgSQL provides the EXECUTE
statement for these occasions. statement for these occasions.
</para> </para>
<para> <para>
<synopsis> <synopsis>
EXECUTE <replaceable class="command">query-string</replaceable> EXECUTE <replaceable class="command">query-string</replaceable>
</synopsis> </synopsis>
where <replaceable>query-string</replaceable> is a string of where <replaceable>query-string</replaceable> is a string of type
type <type>text</type> containing the <replaceable>query</replaceable> to be <type>text</type> containing the <replaceable>query</replaceable>
executed. to be executed.
</para> </para>
<para> <para>
When working with dynamic queries you will have to face When working with dynamic queries you will have to face
escaping of single quotes in PL/pgSQL. Please refer to the escaping of single quotes in PL/pgSQL. Please refer to the
...@@ -707,32 +737,33 @@ EXECUTE <replaceable class="command">query-string</replaceable> ...@@ -707,32 +737,33 @@ EXECUTE <replaceable class="command">query-string</replaceable>
<para> <para>
Unlike all other queries in PL/pgSQL, a Unlike all other queries in PL/pgSQL, a
<replaceable>query</replaceable> run by an EXECUTE statement <replaceable>query</replaceable> run by an EXECUTE statement is
is not prepared and saved just once during the life of the not prepared and saved just once during the life of the server.
server. Instead, the <replaceable>query</replaceable> is Instead, the <replaceable>query</replaceable> is prepared each
prepared each time the statement is run. The time the statement is run. The
<replaceable>query-string</replaceable> can be dynamically created <replaceable>query-string</replaceable> can be dynamically
within the procedure to perform actions on variable tables and created within the procedure to perform actions on variable
fields. tables and fields.
</para> </para>
<para> <para>
The results from SELECT queries are discarded by EXECUTE, and The results from SELECT queries are discarded by EXECUTE, and
SELECT INTO is not currently supported within EXECUTE. So, the SELECT INTO is not currently supported within EXECUTE. So, the
only way to extract a result from a dynamically-created SELECT only way to extract a result from a dynamically-created SELECT is
is to use the FOR ... EXECUTE form described later. to use the FOR ... EXECUTE form described later.
</para> </para>
<para> <para>
An example: An example:
<synopsis> <synopsis>
EXECUTE ''UPDATE tbl SET '' EXECUTE ''UPDATE tbl SET ''
|| quote_ident(fieldname) || quote_ident(fieldname)
|| '' = '' || '' = ''
|| quote_literal(newvalue) || quote_literal(newvalue)
|| '' WHERE ...''; || '' WHERE ...'';
</synopsis> </synopsis>
</para> </para>
<para> <para>
This example shows use of the functions This example shows use of the functions
<function>quote_ident</function>(<type>TEXT</type>) and <function>quote_ident</function>(<type>TEXT</type>) and
...@@ -745,9 +776,10 @@ EXECUTE ''UPDATE tbl SET '' ...@@ -745,9 +776,10 @@ EXECUTE ''UPDATE tbl SET ''
appropriate steps to return the input text enclosed in single appropriate steps to return the input text enclosed in single
or double quotes and with any embedded special characters. or double quotes and with any embedded special characters.
</para> </para>
<para> <para>
Here is a much larger example of a dynamic query and EXECUTE: Here is a much larger example of a dynamic query and EXECUTE:
<programlisting> <programlisting>
CREATE FUNCTION cs_update_referrer_type_proc() RETURNS INTEGER AS ' CREATE FUNCTION cs_update_referrer_type_proc() RETURNS INTEGER AS '
DECLARE DECLARE
referrer_keys RECORD; -- Declare a generic record to be used in a FOR referrer_keys RECORD; -- Declare a generic record to be used in a FOR
...@@ -778,26 +810,29 @@ BEGIN ...@@ -778,26 +810,29 @@ BEGIN
EXECUTE a_output; EXECUTE a_output;
end; end;
' language 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</para> </para>
</sect3> </sect3>
<sect3 id="plpgsql-statements-diagnostics"> <sect3 id="plpgsql-statements-diagnostics">
<title>Obtaining other results status</title> <title>Obtaining other results status</title>
<para> <para>
<synopsis> <synopsis>
GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional> GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional>
</synopsis> </synopsis>
This command allows retrieval of system status indicators. Each This command allows retrieval of system status indicators. Each
<replaceable>item</replaceable> is a keyword identifying a state <replaceable>item</replaceable> is a keyword identifying a state
value to be assigned to the specified variable (which should be of value to be assigned to the specified variable (which should be
the right datatype to receive it). The currently available status of the right datatype to receive it). The currently available
items are <varname>ROW_COUNT</>, the number of rows processed by status items are <varname>ROW_COUNT</>, the number of rows
the last <acronym>SQL</acronym> query sent down to the <acronym>SQL</acronym> engine; and processed by the last <acronym>SQL</acronym> query sent down to
<varname>RESULT_OID</>, the Oid of the last row inserted by the the <acronym>SQL</acronym> engine; and <varname>RESULT_OID</>,
most recent <acronym>SQL</acronym> query. Note that <varname>RESULT_OID</> is only the Oid of the last row inserted by the most recent
useful after an INSERT query. <acronym>SQL</acronym> query. Note that <varname>RESULT_OID</>
is only useful after an INSERT query.
</para> </para>
</sect3> </sect3>
...@@ -805,15 +840,17 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace ...@@ -805,15 +840,17 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
<title>Returning from a function</title> <title>Returning from a function</title>
<para> <para>
<synopsis> <synopsis>
RETURN <replaceable>expression</replaceable> RETURN <replaceable>expression</replaceable>
</synopsis> </synopsis>
The function terminates and the value of <replaceable>expression</replaceable> The function terminates and the value of
will be returned to the upper executor. The return value of a function <replaceable>expression</replaceable> will be returned to the
cannot be undefined. If control reaches the end of the top-level block upper executor. The return value of a function cannot be
of the function without hitting a RETURN statement, a runtime error undefined. If control reaches the end of the top-level block of
the function without hitting a RETURN statement, a runtime error
will occur. will occur.
</para> </para>
<para> <para>
The expressions result will be automatically casted into the The expressions result will be automatically casted into the
function's return type as described for assignments. function's return type as described for assignments.
...@@ -833,7 +870,7 @@ RETURN <replaceable>expression</replaceable> ...@@ -833,7 +870,7 @@ RETURN <replaceable>expression</replaceable>
flexible and powerful way. flexible and powerful way.
</para> </para>
<sect3 id="plpgsql-description-control-structures-conditionals"> <sect3 id="plpgsql-description-conditionals">
<title>Conditional Control: IF statements</title> <title>Conditional Control: IF statements</title>
<para> <para>
...@@ -850,17 +887,18 @@ RETURN <replaceable>expression</replaceable> ...@@ -850,17 +887,18 @@ RETURN <replaceable>expression</replaceable>
<term> <term>
IF-THEN IF-THEN
</term> </term>
<listitem> <listitem>
<para> <para>
IF-THEN statements is the simplest form of an IF. The IF-THEN statements is the simplest form of an IF. The
statements between THEN and END IF will be executed if statements between THEN and END IF will be executed if
the condition is true. Otherwise, the statements the condition is true. Otherwise, the statements
following END IF will be executed. following END IF will be executed.
<programlisting> <programlisting>
IF v_user_id &lt;&gt; 0 THEN IF v_user_id &lt;&gt; 0 THEN
UPDATE users SET email = v_email WHERE user_id = v_user_id; UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF; END IF;
</programlisting> </programlisting>
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -869,12 +907,13 @@ END IF; ...@@ -869,12 +907,13 @@ END IF;
<term> <term>
IF-THEN-ELSE IF-THEN-ELSE
</term> </term>
<listitem> <listitem>
<para> <para>
IF-THEN-ELSE statements adds to IF-THEN by letting you IF-THEN-ELSE statements adds to IF-THEN by letting you
specify the statements that should be executed if the specify the statements that should be executed if the
condition evaluates to FALSE. condition evaluates to FALSE.
<programlisting> <programlisting>
IF parentid IS NULL or parentid = '''' IF parentid IS NULL or parentid = ''''
THEN THEN
return fullname; return fullname;
...@@ -889,12 +928,13 @@ IF v_count > 0 THEN ...@@ -889,12 +928,13 @@ IF v_count > 0 THEN
ELSE ELSE
return ''f''; return ''f'';
END IF; END IF;
</programlisting> </programlisting>
</para> </para>
<para> <para>
IF statements can be nested and in the following IF statements can be nested and in the following
example: example:
<programlisting> <programlisting>
IF demo_row.sex = ''m'' THEN IF demo_row.sex = ''m'' THEN
pretty_sex := ''man''; pretty_sex := ''man'';
ELSE ELSE
...@@ -902,7 +942,7 @@ ELSE ...@@ -902,7 +942,7 @@ ELSE
pretty_sex := ''woman''; pretty_sex := ''woman'';
END IF; END IF;
END IF; END IF;
</programlisting> </programlisting>
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -911,6 +951,7 @@ END IF; ...@@ -911,6 +951,7 @@ END IF;
<term> <term>
IF-THEN-ELSE IF IF-THEN-ELSE IF
</term> </term>
<listitem> <listitem>
<para> <para>
When you use the "ELSE IF" statement, you are actually When you use the "ELSE IF" statement, you are actually
...@@ -918,16 +959,17 @@ END IF; ...@@ -918,16 +959,17 @@ END IF;
statement. Thus you need one END IF statement for each statement. Thus you need one END IF statement for each
nested IF and one for the parent IF-ELSE. nested IF and one for the parent IF-ELSE.
</para> </para>
<para> <para>
For example: For example:
<programlisting> <programlisting>
IF demo_row.sex = ''m'' THEN IF demo_row.sex = ''m'' THEN
pretty_sex := ''man''; pretty_sex := ''man'';
ELSE IF demo_row.sex = ''f'' THEN ELSE IF demo_row.sex = ''f'' THEN
pretty_sex := ''woman''; pretty_sex := ''woman'';
END IF; END IF;
END IF; END IF;
</programlisting> </programlisting>
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -942,19 +984,21 @@ END IF; ...@@ -942,19 +984,21 @@ END IF;
control the flow of execution of your PL/pgSQL program control the flow of execution of your PL/pgSQL program
iteratively. iteratively.
</para> </para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term> <term>
LOOP LOOP
</term> </term>
<listitem> <listitem>
<para> <para>
<synopsis> <synopsis>
<optional>&lt;&lt;label&gt;&gt;</optional> <optional>&lt;&lt;label&gt;&gt;</optional>
LOOP LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP;
</synopsis> </synopsis>
An unconditional loop that must be terminated explicitly An unconditional loop that must be terminated explicitly
by an EXIT statement. The optional label can be used by by an EXIT statement. The optional label can be used by
EXIT statements of nested loops to specify which level of EXIT statements of nested loops to specify which level of
...@@ -967,11 +1011,12 @@ END LOOP; ...@@ -967,11 +1011,12 @@ END LOOP;
<term> <term>
EXIT EXIT
</term> </term>
<listitem> <listitem>
<para> <para>
<synopsis> <synopsis>
EXIT <optional> <replaceable>label</replaceable> </optional> <optional> WHEN <replaceable>expression</replaceable> </optional>; EXIT <optional> <replaceable>label</replaceable> </optional> <optional> WHEN <replaceable>expression</replaceable> </optional>;
</synopsis> </synopsis>
If no <replaceable>label</replaceable> is given, If no <replaceable>label</replaceable> is given,
the innermost loop is terminated and the the innermost loop is terminated and the
statement following END LOOP is executed next. statement following END LOOP is executed next.
...@@ -981,9 +1026,10 @@ EXIT <optional> <replaceable>label</replaceable> </optional> <optional> WHEN <re ...@@ -981,9 +1026,10 @@ EXIT <optional> <replaceable>label</replaceable> </optional> <optional> WHEN <re
continues with the statement after the loops/blocks corresponding continues with the statement after the loops/blocks corresponding
END. END.
</para> </para>
<para> <para>
Examples: Examples:
<programlisting> <programlisting>
LOOP LOOP
-- some computations -- some computations
IF count > 0 THEN IF count > 0 THEN
...@@ -1002,7 +1048,7 @@ BEGIN ...@@ -1002,7 +1048,7 @@ BEGIN
EXIT; -- illegal. Can't use EXIT outside of a LOOP EXIT; -- illegal. Can't use EXIT outside of a LOOP
END IF; END IF;
END; END;
</programlisting> </programlisting>
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1011,23 +1057,20 @@ END; ...@@ -1011,23 +1057,20 @@ END;
<term> <term>
WHILE WHILE
</term> </term>
<listitem> <listitem>
<para> <para>
With the WHILE statement, you can loop through a With the WHILE statement, you can loop through a
sequence of statements as long as the evaluation of sequence of statements as long as the evaluation of
the condition expression is true. the condition expression is true.
</para> <synopsis>
<para>
<synopsis>
<optional>&lt;&lt;label&gt;&gt;</optional> <optional>&lt;&lt;label&gt;&gt;</optional>
WHILE <replaceable>expression</replaceable> LOOP WHILE <replaceable>expression</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP;
</synopsis> </synopsis>
<para>
For example: For example:
</para> <programlisting>
<programlisting>
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- some computations here -- some computations here
END LOOP; END LOOP;
...@@ -1035,7 +1078,7 @@ END LOOP; ...@@ -1035,7 +1078,7 @@ END LOOP;
WHILE NOT boolean_expression LOOP WHILE NOT boolean_expression LOOP
-- some computations here -- some computations here
END LOOP; END LOOP;
</programlisting> </programlisting>
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1044,24 +1087,27 @@ END LOOP; ...@@ -1044,24 +1087,27 @@ END LOOP;
<term> <term>
FOR FOR
</term> </term>
<listitem> <listitem>
<para> <para>
<synopsis> <synopsis>
<optional>&lt;&lt;label&gt;&gt;</optional> <optional>&lt;&lt;label&gt;&gt;</optional>
FOR <replaceable>name</replaceable> IN <optional> REVERSE </optional> <replaceable>expression</replaceable> .. <replaceable>expression</replaceable> LOOP FOR <replaceable>name</replaceable> IN <optional> REVERSE </optional> <replaceable>expression</replaceable> .. <replaceable>expression</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP;
</synopsis> </synopsis>
A loop that iterates over a range of integer values. The variable A loop that iterates over a range of integer values. The variable
<replaceable>name</replaceable> is automatically created as type <replaceable>name</replaceable> is automatically created as type
integer and exists only inside the loop. The two expressions giving integer and exists only inside the loop. The two expressions giving
the lower and upper bound of the range are evaluated only when entering the lower and upper bound of the range are evaluated only when entering
the loop. The iteration step is always 1. the loop. The iteration step is always 1.
</para> </para>
<para> <para>
Some examples of FOR loops (see <xref linkend="plpgsql-description-records"></xref> for Some examples of FOR loops (see <xref
iterating over records in FOR loops): linkend="plpgsql-description-records"> for iterating over
<programlisting> records in FOR loops):
<programlisting>
FOR i IN 1..10 LOOP FOR i IN 1..10 LOOP
-- some expressions here -- some expressions here
...@@ -1071,7 +1117,7 @@ END LOOP; ...@@ -1071,7 +1117,7 @@ END LOOP;
FOR i IN REVERSE 1..10 LOOP FOR i IN REVERSE 1..10 LOOP
-- some expressions here -- some expressions here
END LOOP; END LOOP;
</programlisting> </programlisting>
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1083,6 +1129,7 @@ END LOOP; ...@@ -1083,6 +1129,7 @@ END LOOP;
<sect2 id="plpgsql-description-records"> <sect2 id="plpgsql-description-records">
<title>Working with RECORDs</title> <title>Working with RECORDs</title>
<para> <para>
Records are similar to rowtypes, but they have no predefined structure. Records are similar to rowtypes, but they have no predefined structure.
They are used in selections and FOR loops to hold one actual They are used in selections and FOR loops to hold one actual
...@@ -1091,46 +1138,51 @@ END LOOP; ...@@ -1091,46 +1138,51 @@ END LOOP;
<sect3 id="plpgsql-description-records-declaration"> <sect3 id="plpgsql-description-records-declaration">
<title>Declaration</title> <title>Declaration</title>
<para> <para>
One variables of type RECORD can be used for different One variables of type RECORD can be used for different
selections. Accessing a record or an attempt to assign selections. Accessing a record or an attempt to assign
a value to a record field when there is no actual row in it results a value to a record field when there is no actual row in it results
in a runtime error. They can be declared like this: in a runtime error. They can be declared like this:
</para> </para>
<para> <para>
<synopsis> <synopsis>
<replaceable>name</replaceable> RECORD; <replaceable>name</replaceable> RECORD;
</synopsis> </synopsis>
</para> </para>
</sect3> </sect3>
<sect3 id="plpgsql-description-records-assignment"> <sect3 id="plpgsql-description-records-assignment">
<title>Assignments</title> <title>Assignments</title>
<para> <para>
An assignment of a complete selection into a record or row can An assignment of a complete selection into a record or row can
be done by: be done by:
<synopsis> <synopsis>
SELECT INTO <replaceable>target</replaceable> <replaceable>expressions</replaceable> FROM ...; SELECT INTO <replaceable>target</replaceable> <replaceable>expressions</replaceable> FROM ...;
</synopsis> </synopsis>
<replaceable>target</replaceable> can be a record, a row variable or a <replaceable>target</replaceable> can be a record, a row variable
comma separated list of variables and record-/row-fields. Note that or a comma separated list of variables and
this is quite different from Postgres' normal interpretation of record-/row-fields. Note that this is quite different from
SELECT INTO, which is that the INTO target is a newly created table. Postgres' normal interpretation of SELECT INTO, which is that the
(If you want to create a table from a SELECT result inside a PL/pgSQL INTO target is a newly created table. (If you want to create a
function, use the equivalent syntax <command>CREATE TABLE AS SELECT</command>.) table from a SELECT result inside a PL/pgSQL function, use the
equivalent syntax <command>CREATE TABLE AS SELECT</command>.)
</para> </para>
<para> <para>
If a row or a variable list is used as target, the selected values If a row or a variable list is used as target, the selected values
must exactly match the structure of the target(s) or a runtime error must exactly match the structure of the target(s) or a runtime error
occurs. The FROM keyword can be followed by any valid qualification, occurs. The FROM keyword can be followed by any valid qualification,
grouping, sorting etc. that can be given for a SELECT statement. grouping, sorting etc. that can be given for a SELECT statement.
</para> </para>
<para> <para>
Once a record or row has been assigned to a RECORD variable, Once a record or row has been assigned to a RECORD variable,
you can use the "." (dot) notation to access fields in that you can use the "." (dot) notation to access fields in that
record: record:
</para> <programlisting>
<para>
<programlisting>
DECLARE DECLARE
users_rec RECORD; users_rec RECORD;
full_name varchar; full_name varchar;
...@@ -1138,26 +1190,29 @@ BEGIN ...@@ -1138,26 +1190,29 @@ BEGIN
SELECT INTO users_rec * FROM users WHERE user_id=3; SELECT INTO users_rec * FROM users WHERE user_id=3;
full_name := users_rec.first_name || '' '' || users_rec.last_name; full_name := users_rec.first_name || '' '' || users_rec.last_name;
</programlisting> </programlisting>
</para> </para>
<para> <para>
There is a special variable named FOUND of type <type>boolean</type> that can be used There is a special variable named FOUND of type
immediately after a SELECT INTO to check if an assignment had success. <type>boolean</type> that can be used immediately after a SELECT
INTO to check if an assignment had success.
<programlisting> <programlisting>
SELECT INTO myrec * FROM EMP WHERE empname = myname; SELECT INTO myrec * FROM EMP WHERE empname = myname;
IF NOT FOUND THEN IF NOT FOUND THEN
RAISE EXCEPTION ''employee % not found'', myname; RAISE EXCEPTION ''employee % not found'', myname;
END IF; END IF;
</programlisting> </programlisting>
You can also use the IS NULL (or ISNULL) conditionals to You can also use the IS NULL (or ISNULL) conditionals to
test for NULLity of a RECORD/ROW. If the selection returns test for NULLity of a RECORD/ROW. If the selection returns
multiple rows, only the first is moved into the target multiple rows, only the first is moved into the target
fields. All others are silently discarded. fields. All others are silently discarded.
</para> </para>
<para> <para>
<programlisting> <programlisting>
DECLARE DECLARE
users_rec RECORD; users_rec RECORD;
full_name varchar; full_name varchar;
...@@ -1170,9 +1225,10 @@ BEGIN ...@@ -1170,9 +1225,10 @@ BEGIN
return ''http://''; return ''http://'';
END IF; END IF;
END; END;
</programlisting> </programlisting>
</para> </para>
</sect3> </sect3>
<sect3 id="plpgsql-description-records-iterating"> <sect3 id="plpgsql-description-records-iterating">
<title>Iterating Through Records</title> <title>Iterating Through Records</title>
...@@ -1180,20 +1236,19 @@ END; ...@@ -1180,20 +1236,19 @@ END;
Using a special type of FOR loop, you can iterate through Using a special type of FOR loop, you can iterate through
the results of a query and manipulate that data the results of a query and manipulate that data
accordingly. The syntax is as follow: accordingly. The syntax is as follow:
</para> <synopsis>
<para>
<synopsis>
<optional>&lt;&lt;label&gt;&gt;</optional> <optional>&lt;&lt;label&gt;&gt;</optional>
FOR <replaceable>record | row</replaceable> IN <replaceable>select_clause</replaceable> LOOP FOR <replaceable>record | row</replaceable> IN <replaceable>select_clause</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP;
</synopsis> </synopsis>
The record or row is assigned all the rows The record or row is assigned all the rows
resulting from the select clause and the loop body executed resulting from the select clause and the loop body executed
for each. Here is an example: for each. Here is an example:
</para> </para>
<para> <para>
<programlisting> <programlisting>
create function cs_refresh_mviews () returns integer as ' create function cs_refresh_mviews () returns integer as '
DECLARE DECLARE
mviews RECORD; mviews RECORD;
...@@ -1218,22 +1273,21 @@ BEGIN ...@@ -1218,22 +1273,21 @@ BEGIN
return 1; return 1;
end; end;
' language 'plpgsql'; ' language 'plpgsql';
</programlisting> </programlisting>
If the loop is terminated with an EXIT statement, If the loop is terminated with an EXIT statement, the last
the last assigned row is still accessible after the loop. assigned row is still accessible after the loop.
</para> </para>
<para> <para>
The FOR-IN EXECUTE statement is another way to iterate over The FOR-IN EXECUTE statement is another way to iterate over
records: records:
</para> <synopsis>
<para>
<synopsis>
<optional>&lt;&lt;label&gt;&gt;</optional> <optional>&lt;&lt;label&gt;&gt;</optional>
FOR <replaceable>record | row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP FOR <replaceable>record | row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP;
</synopsis> </synopsis>
This is like the previous form, except that the source SELECT This is like the previous form, except that the source SELECT
statement is specified as a string expression, which is evaluated statement is specified as a string expression, which is evaluated
and re-planned on each entry to the FOR loop. This allows the and re-planned on each entry to the FOR loop. This allows the
...@@ -1251,27 +1305,30 @@ END LOOP; ...@@ -1251,27 +1305,30 @@ END LOOP;
Use the RAISE statement to throw messages into the Use the RAISE statement to throw messages into the
<productname>Postgres</productname> elog mechanism. <productname>Postgres</productname> elog mechanism.
<synopsis> <synopsis>
RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">identifier</replaceable> <optional>...</optional></optional>; RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">identifier</replaceable> <optional>...</optional></optional>;
</synopsis> </synopsis>
Inside the format, <literal>%</literal> is used as a placeholder for the Inside the format, <literal>%</literal> is used as a placeholder for the
subsequent comma-separated identifiers. Possible levels are subsequent comma-separated identifiers. Possible levels are
DEBUG (silently suppressed in production running databases), NOTICE DEBUG (silently suppressed in production running databases), NOTICE
(written into the database log and forwarded to the client application) (written into the database log and forwarded to the client application)
and EXCEPTION (written into the database log and aborting the transaction). and EXCEPTION (written into the database log and aborting the transaction).
</para> </para>
<para> <para>
<programlisting> <programlisting>
RAISE NOTICE ''Id number '' || key || '' not found!''; RAISE NOTICE ''Id number '' || key || '' not found!'';
RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id; RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;
</programlisting> </programlisting>
In this last example, v_job_id will replace the % in the In this last example, v_job_id will replace the % in the
string. string.
</para> </para>
<para> <para>
<programlisting> <programlisting>
RAISE EXCEPTION ''Inexistent ID --> %'',user_id; RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
</programlisting> </programlisting>
This will abort the transaction and write to the database log. This will abort the transaction and write to the database log.
</para> </para>
</sect2> </sect2>
...@@ -1288,6 +1345,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1288,6 +1345,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
the whole transaction gets aborted and the system jumps back the whole transaction gets aborted and the system jumps back
into the main loop to get the next query from the client application. into the main loop to get the next query from the client application.
</para> </para>
<para> <para>
It is possible to hook into the error mechanism to notice that this It is possible to hook into the error mechanism to notice that this
happens. But currently it is impossible to tell what really happens. But currently it is impossible to tell what really
...@@ -1299,6 +1357,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1299,6 +1357,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
is aborted, is already sent to the client application, so resuming is aborted, is already sent to the client application, so resuming
operation does not make any sense. operation does not make any sense.
</para> </para>
<para> <para>
Thus, the only thing PL/pgSQL currently does when it encounters Thus, the only thing PL/pgSQL currently does when it encounters
an abort during execution of a function or trigger an abort during execution of a function or trigger
...@@ -1315,21 +1374,20 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1315,21 +1374,20 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
<sect1 id="plpgsql-trigger"> <sect1 id="plpgsql-trigger">
<title>Trigger Procedures</title> <title>Trigger Procedures</title>
<sect2 id="plpgsql-trigger-description">
<title>Description</title>
<para> <para>
PL/pgSQL can be used to define trigger procedures. They are created PL/pgSQL can be used to define trigger procedures. They are created
with the usual <command>CREATE FUNCTION</command> command as a function with no with the usual <command>CREATE FUNCTION</command> command as a function with no
arguments and a return type of <type>OPAQUE</type>. arguments and a return type of <type>OPAQUE</type>.
</para> </para>
<para> <para>
There are some <productname>Postgres</productname> specific details There are some <productname>Postgres</productname> specific details
in functions used as trigger procedures. in functions used as trigger procedures.
</para> </para>
<para> <para>
First they have some special variables created automatically in the First they have some special variables created automatically in the
top-level blocks declaration section. They are top-level blocks declaration section. They are
</para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
...@@ -1438,6 +1496,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1438,6 +1496,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
</para>
<para> <para>
Second they must return either NULL or a record/row containing Second they must return either NULL or a record/row containing
...@@ -1450,7 +1509,6 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1450,7 +1509,6 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
in NEW and return that or to build a complete new record/row to in NEW and return that or to build a complete new record/row to
return. return.
</para> </para>
</sect2>
<example> <example>
<title>A PL/pgSQL Trigger Procedure Example</title> <title>A PL/pgSQL Trigger Procedure Example</title>
...@@ -1461,12 +1519,13 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; ...@@ -1461,12 +1519,13 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
row. And it ensures that an employees name is given and that the row. And it ensures that an employees name is given and that the
salary is a positive value. salary is a positive value.
<programlisting> <programlisting>
CREATE TABLE emp ( CREATE TABLE emp (
empname text, empname text,
salary integer, salary integer,
last_date timestamp, last_date timestamp,
last_user text); last_user text
);
CREATE FUNCTION emp_stamp () RETURNS OPAQUE AS ' CREATE FUNCTION emp_stamp () RETURNS OPAQUE AS '
BEGIN BEGIN
...@@ -1492,7 +1551,7 @@ CREATE FUNCTION emp_stamp () RETURNS OPAQUE AS ' ...@@ -1492,7 +1551,7 @@ CREATE FUNCTION emp_stamp () RETURNS OPAQUE AS '
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp(); FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
</programlisting> </programlisting>
</para> </para>
</example> </example>
</sect1> </sect1>
...@@ -1519,10 +1578,10 @@ CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp ...@@ -1519,10 +1578,10 @@ CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
in future versions of <productname>Postgres</productname> will be in future versions of <productname>Postgres</productname> will be
forward compatible. forward compatible.
</para> </para>
<para> <para>
For a detailed explanation and examples of how to escape single For a detailed explanation and examples of how to escape single
quotes in different situations, please see <xref linkend="plpgsql-quote"></xref> in quotes in different situations, please see <xref linkend="plpgsql-quote">.
<xref linkend="plpgsql-porting">Porting From Oracle PL/SQL</xref>.
</para> </para>
<example> <example>
...@@ -1535,13 +1594,13 @@ CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp ...@@ -1535,13 +1594,13 @@ CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
one, returning the incremented value. one, returning the incremented value.
</para> </para>
<programlisting> <programlisting>
CREATE FUNCTION add_one (integer) RETURNS integer AS ' CREATE FUNCTION add_one (integer) RETURNS integer AS '
BEGIN BEGIN
RETURN $1 + 1; RETURN $1 + 1;
END; END;
' LANGUAGE 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</example> </example>
<example> <example>
...@@ -1552,29 +1611,30 @@ CREATE FUNCTION add_one (integer) RETURNS integer AS ' ...@@ -1552,29 +1611,30 @@ CREATE FUNCTION add_one (integer) RETURNS integer AS '
returns the result of concatenating them. returns the result of concatenating them.
</para> </para>
<programlisting> <programlisting>
CREATE FUNCTION concat_text (text, text) RETURNS text AS ' CREATE FUNCTION concat_text (text, text) RETURNS text AS '
BEGIN BEGIN
RETURN $1 || $2; RETURN $1 || $2;
END; END;
' LANGUAGE 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</example> </example>
<example> <example>
<title>A PL/pgSQL Function on Composite Type</title> <title>A PL/pgSQL Function on Composite Type</title>
<para> <para>
In this example, we take EMP (a table) and an <type>integer</type> as In this example, we take EMP (a table) and an
arguments to our function, which returns a <type>boolean</type>. If the <type>integer</type> as arguments to our function, which returns
"salary" field of the EMP table is <literal>NULL</literal>, we return a <type>boolean</type>. If the "salary" field of the EMP table is
"f". Otherwise we compare with that field with the <type>integer</type> <literal>NULL</literal>, we return "f". Otherwise we compare with
passed to the function and return the <type>boolean</type> result of the that field with the <type>integer</type> passed to the function
comparison (t or f). This is the PL/pgSQL equivalent to the and return the <type>boolean</type> result of the comparison (t
example from the C functions. or f). This is the PL/pgSQL equivalent to the example from the C
functions.
</para> </para>
<para>
<programlisting> <programlisting>
CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS ' CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS '
DECLARE DECLARE
emprec ALIAS FOR $1; emprec ALIAS FOR $1;
...@@ -1586,8 +1646,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS ' ...@@ -1586,8 +1646,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS '
RETURN emprec.salary > sallim; RETURN emprec.salary > sallim;
END; END;
' LANGUAGE 'plpgsql'; ' LANGUAGE 'plpgsql';
</programlisting> </programlisting>
</para>
</example> </example>
</sect1> </sect1>
...@@ -1681,7 +1740,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS ' ...@@ -1681,7 +1740,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS '
<listitem> <listitem>
<para> <para>
In PostgreSQL you <emphasis>need</emphasis> to escape single In PostgreSQL you <emphasis>need</emphasis> to escape single
quotes. See <xref linkend="plpgsql-quote"></xref>. quotes. See <xref linkend="plpgsql-quote">.
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
...@@ -1695,7 +1754,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS ' ...@@ -1695,7 +1754,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS '
function definition. This can lead to quite amusing code at function definition. This can lead to quite amusing code at
times, especially if you are creating a function that generates times, especially if you are creating a function that generates
other function(s), as in other function(s), as in
<xref linkend="plpgsql-porting-nastyquote">this example</xref>. <xref linkend="plpgsql-porting-nastyquote">.
One thing to keep in mind One thing to keep in mind
when escaping lots of single quotes is that, except for the when escaping lots of single quotes is that, except for the
beginning/ending quotes, all the others will come in even beginning/ending quotes, all the others will come in even
...@@ -1703,7 +1762,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS ' ...@@ -1703,7 +1762,7 @@ CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS '
</para> </para>
<para> <para>
<xref linkend="plpgsql-quoting-table"></xref> gives the scoop. (You'll <xref linkend="plpgsql-quoting-table"> gives the scoop. (You'll
love this little chart.) love this little chart.)
</para> </para>
...@@ -1776,7 +1835,7 @@ a_output := a_output || '' AND name ...@@ -1776,7 +1835,7 @@ a_output := a_output || '' AND name
(which accounts for 8 quotes) <emphasis>and</emphasis> (which accounts for 8 quotes) <emphasis>and</emphasis>
terminate that string (2 more). You will probably only need terminate that string (2 more). You will probably only need
that if you were using a function to generate other functions that if you were using a function to generate other functions
(like in <xref linkend="plpgsql-porting-nastyquote"></xref>). (like in <xref linkend="plpgsql-porting-nastyquote">).
</entry> </entry>
<entry><programlisting> <entry><programlisting>
a_output := a_output || '' if v_'' || a_output := a_output || '' if v_'' ||
...@@ -1865,7 +1924,7 @@ SHOW ERRORS; ...@@ -1865,7 +1924,7 @@ SHOW ERRORS;
On PostgreSQL functions are created using single quotes as On PostgreSQL functions are created using single quotes as
delimiters, so you have to escape single quotes inside your delimiters, so you have to escape single quotes inside your
functions (which can be quite annoying at times; see <xref functions (which can be quite annoying at times; see <xref
linkend="plpgsql-quote">this example</xref>). linkend="plpgsql-quote">).
</para> </para>
</listitem> </listitem>
...@@ -1986,7 +2045,7 @@ end; ...@@ -1986,7 +2045,7 @@ end;
The following Oracle PL/SQL procedure is used to parse a URL and The following Oracle PL/SQL procedure is used to parse a URL and
return several elements (host, path and query). It is an return several elements (host, path and query). It is an
procedure because in PL/pgSQL functions only one value can be returned procedure because in PL/pgSQL functions only one value can be returned
(see <xref linkend="plpgsql-porting-procedures"></xref>). In (see <xref linkend="plpgsql-porting-procedures">). In
PostgreSQL, one way to work around this is to split the procedure PostgreSQL, one way to work around this is to split the procedure
in three different functions: one to return the host, another for in three different functions: one to return the host, another for
the path and another for the query. the path and another for the query.
...@@ -2036,16 +2095,16 @@ show errors; ...@@ -2036,16 +2095,16 @@ show errors;
Here is how this procedure could be translated for PostgreSQL: Here is how this procedure could be translated for PostgreSQL:
<programlisting> <programlisting>
drop function cs_parse_url_host(varchar); drop function cs_parse_url_host(varchar);
create function cs_parse_url_host(varchar) returns varchar as ' create function cs_parse_url_host(varchar) returns varchar as '
declare declare
v_url ALIAS FOR $1; v_url ALIAS FOR $1;
v_host varchar; v_host varchar;
v_path varchar; v_path varchar;
a_pos1 integer; a_pos1 integer;
a_pos2 integer; a_pos2 integer;
a_pos3 integer; a_pos3 integer;
begin begin
v_host := NULL; v_host := NULL;
a_pos1 := instr(v_url,''//''); a_pos1 := instr(v_url,''//'');
...@@ -2062,8 +2121,8 @@ show errors; ...@@ -2062,8 +2121,8 @@ show errors;
v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2 ); v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2 );
return v_host; return v_host;
end; end;
' language 'plpgsql'; ' language 'plpgsql';
</programlisting> </programlisting>
</para> </para>
</example> </example>
...@@ -2075,7 +2134,7 @@ show errors; ...@@ -2075,7 +2134,7 @@ show errors;
I got tired of doing this and created my own I got tired of doing this and created my own
<function>instr</function> functions that behave exactly like <function>instr</function> functions that behave exactly like
Oracle's (it makes life easier). See the <xref Oracle's (it makes life easier). See the <xref
linkend="plpgsql-porting-appendix"></xref> for the code. linkend="plpgsql-porting-appendix"> for the code.
</para> </para>
</note> </note>
</sect2> </sect2>
...@@ -2098,15 +2157,15 @@ show errors; ...@@ -2098,15 +2157,15 @@ show errors;
create or replace procedure cs_create_job(v_job_id in integer) create or replace procedure cs_create_job(v_job_id in integer)
is is
a_running_job_count integer; a_running_job_count integer;
pragma autonomous_transaction;<co id="co.plpgsql-porting-pragma"></co> pragma autonomous_transaction;<co id="co.plpgsql-porting-pragma">
begin begin
lock table cs_jobs in exclusive mode;<co id="co.plpgsql-porting-locktable"></co> lock table cs_jobs in exclusive mode;<co id="co.plpgsql-porting-locktable">
select count(*) into a_running_job_count from cs_jobs select count(*) into a_running_job_count from cs_jobs
where end_stamp is null; where end_stamp is null;
if a_running_job_count > 0 then if a_running_job_count > 0 then
commit; -- free lock<co id="co.plpgsql-porting-commit"></co> commit; -- free lock<co id="co.plpgsql-porting-commit">
raise_application_error(-20000, 'Unable to create a new job: a job is currently running.'); raise_application_error(-20000, 'Unable to create a new job: a job is currently running.');
end if; end if;
...@@ -2115,7 +2174,7 @@ begin ...@@ -2115,7 +2174,7 @@ begin
begin begin
insert into cs_jobs(job_id, start_stamp) values(v_job_id, sysdate); 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<co id="co.plpgsql-porting-exception"></co> exception when dup_val_on_index then null; -- don't worry if it already exists<co id="co.plpgsql-porting-exception">
end; end;
commit; commit;
end; end;
...@@ -2190,7 +2249,7 @@ begin ...@@ -2190,7 +2249,7 @@ begin
insert into cs_jobs(job_id, start_stamp) values(v_job_id, sysdate()); insert into cs_jobs(job_id, start_stamp) values(v_job_id, sysdate());
return 1; return 1;
ELSE ELSE
raise NOTICE ''Job already running.'';<co id="co.plpgsql-porting-raise"></co> raise NOTICE ''Job already running.'';<co id="co.plpgsql-porting-raise">
END IF; END IF;
return 0; return 0;
...@@ -2301,7 +2360,7 @@ END; ...@@ -2301,7 +2360,7 @@ END;
nicely, but you have to remember to use nicely, but you have to remember to use
<function>quote_literal(TEXT)</function> and <function>quote_literal(TEXT)</function> and
<function>quote_string(TEXT)</function> as described in <xref <function>quote_string(TEXT)</function> as described in <xref
linkend="plpgsql-statements"></xref>. Constructs of the type linkend="plpgsql-statements-executing-dyn-queries">. Constructs of the type
<literal>EXECUTE ''SELECT * from $1'';</literal> will not work <literal>EXECUTE ''SELECT * from $1'';</literal> will not work
unless you use these functions. unless you use these functions.
</para> </para>
...@@ -2486,8 +2545,7 @@ CREATE FUNCTION instr(varchar, varchar, integer, integer) RETURNS integer AS ' ...@@ -2486,8 +2545,7 @@ CREATE FUNCTION instr(varchar, varchar, integer, integer) RETURNS integer AS '
return [expr $pos + 1] return [expr $pos + 1]
} }
' LANGUAGE 'pltcl'; ' LANGUAGE 'pltcl';
</programlisting>
</programlisting>
</sect3> </sect3>
</sect2> </sect2>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment