diff options
Diffstat (limited to 'doc/src/sgml/plperl.sgml')
-rw-r--r-- | doc/src/sgml/plperl.sgml | 1595 |
1 files changed, 1595 insertions, 0 deletions
diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml new file mode 100644 index 0000000..25b1077 --- /dev/null +++ b/doc/src/sgml/plperl.sgml @@ -0,0 +1,1595 @@ +<!-- doc/src/sgml/plperl.sgml --> + + <chapter id="plperl"> + <title>PL/Perl — Perl Procedural Language</title> + + <indexterm zone="plperl"> + <primary>PL/Perl</primary> + </indexterm> + + <indexterm zone="plperl"> + <primary>Perl</primary> + </indexterm> + + <para> + PL/Perl is a loadable procedural language that enables you to write + <productname>PostgreSQL</productname> functions and procedures in the + <ulink url="https://www.perl.org">Perl programming language</ulink>. + </para> + + <para> + The main advantage to using PL/Perl is that this allows use, + within stored functions and procedures, of the manyfold <quote>string + munging</quote> operators and functions available for Perl. Parsing + complex strings might be easier using Perl than it is with the + string functions and control structures provided in PL/pgSQL. + </para> + + <para> + To install PL/Perl in a particular database, use + <literal>CREATE EXTENSION plperl</literal>. + </para> + + <tip> + <para> + If a language is installed into <literal>template1</literal>, all subsequently + created databases will have the language installed automatically. + </para> + </tip> + + <note> + <para> + Users of source packages must specially enable the build of + PL/Perl during the installation process. (Refer to <xref + linkend="installation"/> for more information.) Users of + binary packages might find PL/Perl in a separate subpackage. + </para> + </note> + + <sect1 id="plperl-funcs"> + <title>PL/Perl Functions and Arguments</title> + + <para> + To create a function in the PL/Perl language, use the standard + <xref linkend="sql-createfunction"/> + syntax: + +<programlisting> +CREATE FUNCTION <replaceable>funcname</replaceable> (<replaceable>argument-types</replaceable>) +RETURNS <replaceable>return-type</replaceable> +-- function attributes can go here +AS $$ + # PL/Perl function body goes here +$$ LANGUAGE plperl; +</programlisting> + + The body of the function is ordinary Perl code. In fact, the PL/Perl + glue code wraps it inside a Perl subroutine. A PL/Perl function is + called in a scalar context, so it can't return a list. You can return + non-scalar values (arrays, records, and sets) by returning a reference, + as discussed below. + </para> + + <para> + In a PL/Perl procedure, any return value from the Perl code is ignored. + </para> + + <para> + PL/Perl also supports anonymous code blocks called with the + <xref linkend="sql-do"/> statement: + +<programlisting> +DO $$ + # PL/Perl code +$$ LANGUAGE plperl; +</programlisting> + + An anonymous code block receives no arguments, and whatever value it + might return is discarded. Otherwise it behaves just like a function. + </para> + + <note> + <para> + The use of named nested subroutines is dangerous in Perl, especially if + they refer to lexical variables in the enclosing scope. Because a PL/Perl + function is wrapped in a subroutine, any named subroutine you place inside + one will be nested. In general, it is far safer to create anonymous + subroutines which you call via a coderef. For more information, see the + entries for <literal>Variable "%s" will not stay shared</literal> and + <literal>Variable "%s" is not available</literal> in the + <citerefentry><refentrytitle>perldiag</refentrytitle></citerefentry> man page, or + search the Internet for <quote>perl nested named subroutine</quote>. + </para> + </note> + + <para> + The syntax of the <command>CREATE FUNCTION</command> command requires + the function body to be written as a string constant. It is usually + most convenient to use dollar quoting (see <xref + linkend="sql-syntax-dollar-quoting"/>) for the string constant. + If you choose to use escape string syntax <literal>E''</literal>, + you must double any single quote marks (<literal>'</literal>) and backslashes + (<literal>\</literal>) used in the body of the function + (see <xref linkend="sql-syntax-strings"/>). + </para> + + <para> + Arguments and results are handled as in any other Perl subroutine: + arguments are passed in <varname>@_</varname>, and a result value + is returned with <literal>return</literal> or as the last expression + evaluated in the function. + </para> + + <para> + For example, a function returning the greater of two integer values + could be defined as: + +<programlisting> +CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$ + if ($_[0] > $_[1]) { return $_[0]; } + return $_[1]; +$$ LANGUAGE plperl; +</programlisting> + </para> + + <note> + <para> + Arguments will be converted from the database's encoding to UTF-8 + for use inside PL/Perl, and then converted from UTF-8 back to the + database encoding upon return. + </para> + </note> + + <para> + If an SQL null value<indexterm><primary>null value</primary><secondary + sortas="PL/Perl">in PL/Perl</secondary></indexterm> is passed to a function, + the argument value will appear as <quote>undefined</quote> in Perl. The + above function definition will not behave very nicely with null + inputs (in fact, it will act as though they are zeroes). We could + add <literal>STRICT</literal> to the function definition to make + <productname>PostgreSQL</productname> do something more reasonable: + if a null value is passed, the function will not be called at all, + but will just return a null result automatically. Alternatively, + we could check for undefined inputs in the function body. For + example, suppose that we wanted <function>perl_max</function> with + one null and one nonnull argument to return the nonnull argument, + rather than a null value: + +<programlisting> +CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$ + my ($x, $y) = @_; + if (not defined $x) { + return undef if not defined $y; + return $y; + } + return $x if not defined $y; + return $x if $x > $y; + return $y; +$$ LANGUAGE plperl; +</programlisting> + As shown above, to return an SQL null value from a PL/Perl + function, return an undefined value. This can be done whether the + function is strict or not. + </para> + + <para> + Anything in a function argument that is not a reference is + a string, which is in the standard <productname>PostgreSQL</productname> + external text representation for the relevant data type. In the case of + ordinary numeric or text types, Perl will just do the right thing and + the programmer will normally not have to worry about it. However, in + other cases the argument will need to be converted into a form that is + more usable in Perl. For example, the <function>decode_bytea</function> + function can be used to convert an argument of + type <type>bytea</type> into unescaped binary. + </para> + + <para> + Similarly, values passed back to <productname>PostgreSQL</productname> + must be in the external text representation format. For example, the + <function>encode_bytea</function> function can be used to + escape binary data for a return value of type <type>bytea</type>. + </para> + + <para> + One case that is particularly important is boolean values. As just + stated, the default behavior for <type>bool</type> values is that they + are passed to Perl as text, thus either <literal>'t'</literal> + or <literal>'f'</literal>. This is problematic, since Perl will not + treat <literal>'f'</literal> as false! It is possible to improve matters + by using a <quote>transform</quote> (see + <xref linkend="sql-createtransform"/>). Suitable transforms are provided + by the <filename>bool_plperl</filename> extension. To use it, install + the extension: +<programlisting> +CREATE EXTENSION bool_plperl; -- or bool_plperlu for PL/PerlU +</programlisting> + Then use the <literal>TRANSFORM</literal> function attribute for a + PL/Perl function that takes or returns <type>bool</type>, for example: +<programlisting> +CREATE FUNCTION perl_and(bool, bool) RETURNS bool +TRANSFORM FOR TYPE bool +AS $$ + my ($a, $b) = @_; + return $a && $b; +$$ LANGUAGE plperl; +</programlisting> + When this transform is applied, <type>bool</type> arguments will be seen + by Perl as being <literal>1</literal> or empty, thus properly true or + false. If the function result is type <type>bool</type>, it will be true + or false according to whether Perl would evaluate the returned value as + true. + Similar transformations are also performed for boolean query arguments + and results of SPI queries performed inside the function + (<xref linkend="plperl-database"/>). + </para> + + <para> + Perl can return <productname>PostgreSQL</productname> arrays as + references to Perl arrays. Here is an example: + +<programlisting> +CREATE OR REPLACE function returns_array() +RETURNS text[][] AS $$ + return [['a"b','c,d'],['e\\f','g']]; +$$ LANGUAGE plperl; + +select returns_array(); +</programlisting> + </para> + + <para> + Perl passes <productname>PostgreSQL</productname> arrays as a blessed + <type>PostgreSQL::InServer::ARRAY</type> object. This object may be treated as an array + reference or a string, allowing for backward compatibility with Perl + code written for <productname>PostgreSQL</productname> versions below 9.1 to + run. For example: + +<programlisting> +CREATE OR REPLACE FUNCTION concat_array_elements(text[]) RETURNS TEXT AS $$ + my $arg = shift; + my $result = ""; + return undef if (!defined $arg); + + # as an array reference + for (@$arg) { + $result .= $_; + } + + # also works as a string + $result .= $arg; + + return $result; +$$ LANGUAGE plperl; + +SELECT concat_array_elements(ARRAY['PL','/','Perl']); +</programlisting> + + <note> + <para> + Multidimensional arrays are represented as references to + lower-dimensional arrays of references in a way common to every Perl + programmer. + </para> + </note> + </para> + + <para> + Composite-type arguments are passed to the function as references + to hashes. The keys of the hash are the attribute names of the + composite type. Here is an example: + +<programlisting> +CREATE TABLE employee ( + name text, + basesalary integer, + bonus integer +); + +CREATE FUNCTION empcomp(employee) RETURNS integer AS $$ + my ($emp) = @_; + return $emp->{basesalary} + $emp->{bonus}; +$$ LANGUAGE plperl; + +SELECT name, empcomp(employee.*) FROM employee; +</programlisting> + </para> + + <para> + A PL/Perl function can return a composite-type result using the same + approach: return a reference to a hash that has the required attributes. + For example: + +<programlisting> +CREATE TYPE testrowperl AS (f1 integer, f2 text, f3 text); + +CREATE OR REPLACE FUNCTION perl_row() RETURNS testrowperl AS $$ + return {f2 => 'hello', f1 => 1, f3 => 'world'}; +$$ LANGUAGE plperl; + +SELECT * FROM perl_row(); +</programlisting> + + Any columns in the declared result data type that are not present in the + hash will be returned as null values. + </para> + + <para> + Similarly, output arguments of procedures can be returned as a hash + reference: + +<programlisting> +CREATE PROCEDURE perl_triple(INOUT a integer, INOUT b integer) AS $$ + my ($a, $b) = @_; + return {a => $a * 3, b => $b * 3}; +$$ LANGUAGE plperl; + +CALL perl_triple(5, 10); +</programlisting> + </para> + + <para> + PL/Perl functions can also return sets of either scalar or + composite types. Usually you'll want to return rows one at a + time, both to speed up startup time and to keep from queuing up + the entire result set in memory. You can do this with + <function>return_next</function> as illustrated below. Note that + after the last <function>return_next</function>, you must put + either <literal>return</literal> or (better) <literal>return + undef</literal>. + +<programlisting> +CREATE OR REPLACE FUNCTION perl_set_int(int) +RETURNS SETOF INTEGER AS $$ + foreach (0..$_[0]) { + return_next($_); + } + return undef; +$$ LANGUAGE plperl; + +SELECT * FROM perl_set_int(5); + +CREATE OR REPLACE FUNCTION perl_set() +RETURNS SETOF testrowperl AS $$ + return_next({ f1 => 1, f2 => 'Hello', f3 => 'World' }); + return_next({ f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' }); + return_next({ f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }); + return undef; +$$ LANGUAGE plperl; +</programlisting> + + For small result sets, you can return a reference to an array that + contains either scalars, references to arrays, or references to + hashes for simple types, array types, and composite types, + respectively. Here are some simple examples of returning the entire + result set as an array reference: + +<programlisting> +CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$ + return [0..$_[0]]; +$$ LANGUAGE plperl; + +SELECT * FROM perl_set_int(5); + +CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$ + return [ + { f1 => 1, f2 => 'Hello', f3 => 'World' }, + { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' }, + { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' } + ]; +$$ LANGUAGE plperl; + +SELECT * FROM perl_set(); +</programlisting> + </para> + + <para> + If you wish to use the <literal>strict</literal> pragma with your code you + have a few options. For temporary global use you can <command>SET</command> + <literal>plperl.use_strict</literal> to true. + This will affect subsequent compilations of <application>PL/Perl</application> + functions, but not functions already compiled in the current session. + For permanent global use you can set <literal>plperl.use_strict</literal> + to true in the <filename>postgresql.conf</filename> file. + </para> + + <para> + For permanent use in specific functions you can simply put: +<programlisting> +use strict; +</programlisting> + at the top of the function body. + </para> + + <para> + The <literal>feature</literal> pragma is also available to <function>use</function> if your Perl is version 5.10.0 or higher. + </para> + + </sect1> + + <sect1 id="plperl-data"> + <title>Data Values in PL/Perl</title> + + <para> + The argument values supplied to a PL/Perl function's code are + simply the input arguments converted to text form (just as if they + had been displayed by a <command>SELECT</command> statement). + Conversely, the <function>return</function> and <function>return_next</function> + commands will accept any string that is acceptable input format + for the function's declared return type. + </para> + + <para> + If this behavior is inconvenient for a particular case, it can be + improved by using a transform, as already illustrated + for <type>bool</type> values. Several examples of transform modules + are included in the <productname>PostgreSQL</productname> distribution. + </para> + </sect1> + + <sect1 id="plperl-builtins"> + <title>Built-in Functions</title> + + <sect2 id="plperl-database"> + <title>Database Access from PL/Perl</title> + + <para> + Access to the database itself from your Perl function can be done + via the following functions: + </para> + + <variablelist> + <varlistentry> + <term> + <literal><function>spi_exec_query</function>(<replaceable>query</replaceable> [, <replaceable>limit</replaceable>])</literal> + <indexterm> + <primary>spi_exec_query</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + <function>spi_exec_query</function> executes an SQL command and +returns the entire row set as a reference to an array of hash references. +If <replaceable>limit</replaceable> is specified and is greater than zero, +then <function>spi_exec_query</function> retrieves at +most <replaceable>limit</replaceable> rows, much as if the query included +a <literal>LIMIT</literal> clause. Omitting <replaceable>limit</replaceable> +or specifying it as zero results in no row limit. + </para> + + <para> +<emphasis>You should only use this command when you know +that the result set will be relatively small.</emphasis> Here is an +example of a query (<command>SELECT</command> command) with the +optional maximum number of rows: + +<programlisting> +$rv = spi_exec_query('SELECT * FROM my_table', 5); +</programlisting> + This returns up to 5 rows from the table + <literal>my_table</literal>. If <literal>my_table</literal> + has a column <literal>my_column</literal>, you can get that + value from row <literal>$i</literal> of the result like this: +<programlisting> +$foo = $rv->{rows}[$i]->{my_column}; +</programlisting> + The total number of rows returned from a <command>SELECT</command> + query can be accessed like this: +<programlisting> +$nrows = $rv->{processed} +</programlisting> + </para> + + <para> + Here is an example using a different command type: +<programlisting> +$query = "INSERT INTO my_table VALUES (1, 'test')"; +$rv = spi_exec_query($query); +</programlisting> + You can then access the command status (e.g., + <literal>SPI_OK_INSERT</literal>) like this: +<programlisting> +$res = $rv->{status}; +</programlisting> + To get the number of rows affected, do: +<programlisting> +$nrows = $rv->{processed}; +</programlisting> + </para> + + <para> + Here is a complete example: +<programlisting> +CREATE TABLE test ( + i int, + v varchar +); + +INSERT INTO test (i, v) VALUES (1, 'first line'); +INSERT INTO test (i, v) VALUES (2, 'second line'); +INSERT INTO test (i, v) VALUES (3, 'third line'); +INSERT INTO test (i, v) VALUES (4, 'immortal'); + +CREATE OR REPLACE FUNCTION test_munge() RETURNS SETOF test AS $$ + my $rv = spi_exec_query('select i, v from test;'); + my $status = $rv->{status}; + my $nrows = $rv->{processed}; + foreach my $rn (0 .. $nrows - 1) { + my $row = $rv->{rows}[$rn]; + $row->{i} += 200 if defined($row->{i}); + $row->{v} =~ tr/A-Za-z/a-zA-Z/ if (defined($row->{v})); + return_next($row); + } + return undef; +$$ LANGUAGE plperl; + +SELECT * FROM test_munge(); +</programlisting> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>spi_query(<replaceable>command</replaceable>)</function></literal> + <indexterm> + <primary>spi_query</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>spi_fetchrow(<replaceable>cursor</replaceable>)</function></literal> + <indexterm> + <primary>spi_fetchrow</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>spi_cursor_close(<replaceable>cursor</replaceable>)</function></literal> + <indexterm> + <primary>spi_cursor_close</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + + <listitem> + <para> + <literal>spi_query</literal> and <literal>spi_fetchrow</literal> + work together as a pair for row sets which might be large, or for cases + where you wish to return rows as they arrive. + <literal>spi_fetchrow</literal> works <emphasis>only</emphasis> with + <literal>spi_query</literal>. The following example illustrates how + you use them together: + +<programlisting> +CREATE TYPE foo_type AS (the_num INTEGER, the_text TEXT); + +CREATE OR REPLACE FUNCTION lotsa_md5 (INTEGER) RETURNS SETOF foo_type AS $$ + use Digest::MD5 qw(md5_hex); + my $file = '/usr/share/dict/words'; + my $t = localtime; + elog(NOTICE, "opening file $file at $t" ); + open my $fh, '<', $file # ooh, it's a file access! + or elog(ERROR, "cannot open $file for reading: $!"); + my @words = <$fh>; + close $fh; + $t = localtime; + elog(NOTICE, "closed file $file at $t"); + chomp(@words); + my $row; + my $sth = spi_query("SELECT * FROM generate_series(1,$_[0]) AS b(a)"); + while (defined ($row = spi_fetchrow($sth))) { + return_next({ + the_num => $row->{a}, + the_text => md5_hex($words[rand @words]) + }); + } + return; +$$ LANGUAGE plperlu; + +SELECT * from lotsa_md5(500); +</programlisting> + </para> + + <para> + Normally, <function>spi_fetchrow</function> should be repeated until it + returns <literal>undef</literal>, indicating that there are no more + rows to read. The cursor returned by <literal>spi_query</literal> + is automatically freed when + <function>spi_fetchrow</function> returns <literal>undef</literal>. + If you do not wish to read all the rows, instead call + <function>spi_cursor_close</function> to free the cursor. + Failure to do so will result in memory leaks. + </para> + + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>spi_prepare(<replaceable>command</replaceable>, <replaceable>argument types</replaceable>)</function></literal> + <indexterm> + <primary>spi_prepare</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>spi_query_prepared(<replaceable>plan</replaceable>, <replaceable>arguments</replaceable>)</function></literal> + <indexterm> + <primary>spi_query_prepared</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>spi_exec_prepared(<replaceable>plan</replaceable> [, <replaceable>attributes</replaceable>], <replaceable>arguments</replaceable>)</function></literal> + <indexterm> + <primary>spi_exec_prepared</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>spi_freeplan(<replaceable>plan</replaceable>)</function></literal> + <indexterm> + <primary>spi_freeplan</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + + <listitem> + <para> + <literal>spi_prepare</literal>, <literal>spi_query_prepared</literal>, <literal>spi_exec_prepared</literal>, + and <literal>spi_freeplan</literal> implement the same functionality but for prepared queries. + <literal>spi_prepare</literal> accepts a query string with numbered argument placeholders ($1, $2, etc.) + and a string list of argument types: +<programlisting> +$plan = spi_prepare('SELECT * FROM test WHERE id > $1 AND name = $2', + 'INTEGER', 'TEXT'); +</programlisting> + Once a query plan is prepared by a call to <literal>spi_prepare</literal>, the plan can be used instead + of the string query, either in <literal>spi_exec_prepared</literal>, where the result is the same as returned + by <literal>spi_exec_query</literal>, or in <literal>spi_query_prepared</literal> which returns a cursor + exactly as <literal>spi_query</literal> does, which can be later passed to <literal>spi_fetchrow</literal>. + The optional second parameter to <literal>spi_exec_prepared</literal> is a hash reference of attributes; + the only attribute currently supported is <literal>limit</literal>, which + sets the maximum number of rows returned from the query. + Omitting <literal>limit</literal> or specifying it as zero results in no + row limit. + </para> + + <para> + The advantage of prepared queries is that is it possible to use one prepared plan for more + than one query execution. After the plan is not needed anymore, it can be freed with + <literal>spi_freeplan</literal>: +<programlisting> +CREATE OR REPLACE FUNCTION init() RETURNS VOID AS $$ + $_SHARED{my_plan} = spi_prepare('SELECT (now() + $1)::date AS now', + 'INTERVAL'); +$$ LANGUAGE plperl; + +CREATE OR REPLACE FUNCTION add_time( INTERVAL ) RETURNS TEXT AS $$ + return spi_exec_prepared( + $_SHARED{my_plan}, + $_[0] + )->{rows}->[0]->{now}; +$$ LANGUAGE plperl; + +CREATE OR REPLACE FUNCTION done() RETURNS VOID AS $$ + spi_freeplan( $_SHARED{my_plan}); + undef $_SHARED{my_plan}; +$$ LANGUAGE plperl; + +SELECT init(); +SELECT add_time('1 day'), add_time('2 days'), add_time('3 days'); +SELECT done(); + + add_time | add_time | add_time +------------+------------+------------ + 2005-12-10 | 2005-12-11 | 2005-12-12 +</programlisting> + Note that the parameter subscript in <literal>spi_prepare</literal> is defined via + $1, $2, $3, etc., so avoid declaring query strings in double quotes that might easily + lead to hard-to-catch bugs. + </para> + + <para> + Another example illustrates usage of an optional parameter in <literal>spi_exec_prepared</literal>: +<programlisting> +CREATE TABLE hosts AS SELECT id, ('192.168.1.'||id)::inet AS address + FROM generate_series(1,3) AS id; + +CREATE OR REPLACE FUNCTION init_hosts_query() RETURNS VOID AS $$ + $_SHARED{plan} = spi_prepare('SELECT * FROM hosts + WHERE address << $1', 'inet'); +$$ LANGUAGE plperl; + +CREATE OR REPLACE FUNCTION query_hosts(inet) RETURNS SETOF hosts AS $$ + return spi_exec_prepared( + $_SHARED{plan}, + {limit => 2}, + $_[0] + )->{rows}; +$$ LANGUAGE plperl; + +CREATE OR REPLACE FUNCTION release_hosts_query() RETURNS VOID AS $$ + spi_freeplan($_SHARED{plan}); + undef $_SHARED{plan}; +$$ LANGUAGE plperl; + +SELECT init_hosts_query(); +SELECT query_hosts('192.168.1.0/30'); +SELECT release_hosts_query(); + + query_hosts +----------------- + (1,192.168.1.1) + (2,192.168.1.2) +(2 rows) +</programlisting> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>spi_commit()</function></literal> + <indexterm> + <primary>spi_commit</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>spi_rollback()</function></literal> + <indexterm> + <primary>spi_rollback</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Commit or roll back the current transaction. This can only be called + in a procedure or anonymous code block (<command>DO</command> command) + called from the top level. (Note that it is not possible to run the + SQL commands <command>COMMIT</command> or <command>ROLLBACK</command> + via <function>spi_exec_query</function> or similar. It has to be done + using these functions.) After a transaction is ended, a new + transaction is automatically started, so there is no separate function + for that. + </para> + + <para> + Here is an example: +<programlisting> +CREATE PROCEDURE transaction_test1() +LANGUAGE plperl +AS $$ +foreach my $i (0..9) { + spi_exec_query("INSERT INTO test1 (a) VALUES ($i)"); + if ($i % 2 == 0) { + spi_commit(); + } else { + spi_rollback(); + } +} +$$; + +CALL transaction_test1(); +</programlisting> + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="plperl-utility-functions"> + <title>Utility Functions in PL/Perl</title> + + <variablelist> + <varlistentry> + <term> + <literal><function>elog(<replaceable>level</replaceable>, <replaceable>msg</replaceable>)</function></literal> + <indexterm> + <primary>elog</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Emit a log or error message. Possible levels are + <literal>DEBUG</literal>, <literal>LOG</literal>, <literal>INFO</literal>, + <literal>NOTICE</literal>, <literal>WARNING</literal>, and <literal>ERROR</literal>. + <literal>ERROR</literal> + raises an error condition; if this is not trapped by the surrounding + Perl code, the error propagates out to the calling query, causing + the current transaction or subtransaction to be aborted. This + is effectively the same as the Perl <literal>die</literal> command. + The other levels only generate messages of different + priority levels. + Whether messages of a particular priority are reported to the client, + written to the server log, or both is controlled by the + <xref linkend="guc-log-min-messages"/> and + <xref linkend="guc-client-min-messages"/> configuration + variables. See <xref linkend="runtime-config"/> for more + information. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>quote_literal(<replaceable>string</replaceable>)</function></literal> + <indexterm> + <primary>quote_literal</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Return the given string suitably quoted to be used as a string literal in an SQL + statement string. Embedded single-quotes and backslashes are properly doubled. + Note that <function>quote_literal</function> returns undef on undef input; if the argument + might be undef, <function>quote_nullable</function> is often more suitable. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>quote_nullable(<replaceable>string</replaceable>)</function></literal> + <indexterm> + <primary>quote_nullable</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Return the given string suitably quoted to be used as a string literal in an SQL + statement string; or, if the argument is undef, return the unquoted string "NULL". + Embedded single-quotes and backslashes are properly doubled. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>quote_ident(<replaceable>string</replaceable>)</function></literal> + <indexterm> + <primary>quote_ident</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Return the given string suitably quoted to be used as an identifier in + an SQL statement string. Quotes are added only if necessary (i.e., if + the string contains non-identifier characters or would be case-folded). + Embedded quotes are properly doubled. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>decode_bytea(<replaceable>string</replaceable>)</function></literal> + <indexterm> + <primary>decode_bytea</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Return the unescaped binary data represented by the contents of the given string, + which should be <type>bytea</type> encoded. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>encode_bytea(<replaceable>string</replaceable>)</function></literal> + <indexterm> + <primary>encode_bytea</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Return the <type>bytea</type> encoded form of the binary data contents of the given string. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>encode_array_literal(<replaceable>array</replaceable>)</function></literal> + <indexterm> + <primary>encode_array_literal</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <term> + <literal><function>encode_array_literal(<replaceable>array</replaceable>, <replaceable>delimiter</replaceable>)</function></literal> + </term> + <listitem> + <para> + Returns the contents of the referenced array as a string in array literal format + (see <xref linkend="arrays-input"/>). + Returns the argument value unaltered if it's not a reference to an array. + The delimiter used between elements of the array literal defaults to "<literal>, </literal>" + if a delimiter is not specified or is undef. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>encode_typed_literal(<replaceable>value</replaceable>, <replaceable>typename</replaceable>)</function></literal> + <indexterm> + <primary>encode_typed_literal</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Converts a Perl variable to the value of the data type passed as a + second argument and returns a string representation of this value. + Correctly handles nested arrays and values of composite types. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>encode_array_constructor(<replaceable>array</replaceable>)</function></literal> + <indexterm> + <primary>encode_array_constructor</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Returns the contents of the referenced array as a string in array constructor format + (see <xref linkend="sql-syntax-array-constructors"/>). + Individual values are quoted using <function>quote_nullable</function>. + Returns the argument value, quoted using <function>quote_nullable</function>, + if it's not a reference to an array. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>looks_like_number(<replaceable>string</replaceable>)</function></literal> + <indexterm> + <primary>looks_like_number</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Returns a true value if the content of the given string looks like a + number, according to Perl, returns false otherwise. + Returns undef if the argument is undef. Leading and trailing space is + ignored. <literal>Inf</literal> and <literal>Infinity</literal> are regarded as numbers. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <literal><function>is_array_ref(<replaceable>argument</replaceable>)</function></literal> + <indexterm> + <primary>is_array_ref</primary> + <secondary>in PL/Perl</secondary> + </indexterm> + </term> + <listitem> + <para> + Returns a true value if the given argument may be treated as an + array reference, that is, if ref of the argument is <literal>ARRAY</literal> or + <literal>PostgreSQL::InServer::ARRAY</literal>. Returns false otherwise. + </para> + </listitem> + </varlistentry> + + </variablelist> + </sect2> + </sect1> + + <sect1 id="plperl-global"> + <title>Global Values in PL/Perl</title> + + <para> + You can use the global hash <varname>%_SHARED</varname> to store + data, including code references, between function calls for the + lifetime of the current session. + </para> + + <para> + Here is a simple example for shared data: +<programlisting> +CREATE OR REPLACE FUNCTION set_var(name text, val text) RETURNS text AS $$ + if ($_SHARED{$_[0]} = $_[1]) { + return 'ok'; + } else { + return "cannot set shared variable $_[0] to $_[1]"; + } +$$ LANGUAGE plperl; + +CREATE OR REPLACE FUNCTION get_var(name text) RETURNS text AS $$ + return $_SHARED{$_[0]}; +$$ LANGUAGE plperl; + +SELECT set_var('sample', 'Hello, PL/Perl! How''s tricks?'); +SELECT get_var('sample'); +</programlisting> + </para> + + <para> + Here is a slightly more complicated example using a code reference: + +<programlisting> +CREATE OR REPLACE FUNCTION myfuncs() RETURNS void AS $$ + $_SHARED{myquote} = sub { + my $arg = shift; + $arg =~ s/(['\\])/\\$1/g; + return "'$arg'"; + }; +$$ LANGUAGE plperl; + +SELECT myfuncs(); /* initializes the function */ + +/* Set up a function that uses the quote function */ + +CREATE OR REPLACE FUNCTION use_quote(TEXT) RETURNS text AS $$ + my $text_to_quote = shift; + my $qfunc = $_SHARED{myquote}; + return &$qfunc($text_to_quote); +$$ LANGUAGE plperl; +</programlisting> + + (You could have replaced the above with the one-liner + <literal>return $_SHARED{myquote}->($_[0]);</literal> + at the expense of readability.) + </para> + + <para> + For security reasons, PL/Perl executes functions called by any one SQL role + in a separate Perl interpreter for that role. This prevents accidental or + malicious interference by one user with the behavior of another user's + PL/Perl functions. Each such interpreter has its own value of the + <varname>%_SHARED</varname> variable and other global state. Thus, two + PL/Perl functions will share the same value of <varname>%_SHARED</varname> + if and only if they are executed by the same SQL role. In an application + wherein a single session executes code under multiple SQL roles (via + <literal>SECURITY DEFINER</literal> functions, use of <command>SET ROLE</command>, etc.) + you may need to take explicit steps to ensure that PL/Perl functions can + share data via <varname>%_SHARED</varname>. To do that, make sure that + functions that should communicate are owned by the same user, and mark + them <literal>SECURITY DEFINER</literal>. You must of course take care that + such functions can't be used to do anything unintended. + </para> + </sect1> + + <sect1 id="plperl-trusted"> + <title>Trusted and Untrusted PL/Perl</title> + + <indexterm zone="plperl-trusted"> + <primary>trusted</primary> + <secondary>PL/Perl</secondary> + </indexterm> + + <para> + Normally, PL/Perl is installed as a <quote>trusted</quote> programming + language named <literal>plperl</literal>. In this setup, certain Perl + operations are disabled to preserve security. In general, the + operations that are restricted are those that interact with the + environment. This includes file handle operations, + <literal>require</literal>, and <literal>use</literal> (for + external modules). There is no way to access internals of the + database server process or to gain OS-level access with the + permissions of the server process, + as a C function can do. Thus, any unprivileged database user can + be permitted to use this language. + </para> + + <para> + Here is an example of a function that will not work because file + system operations are not allowed for security reasons: +<programlisting> +CREATE FUNCTION badfunc() RETURNS integer AS $$ + my $tmpfile = "/tmp/badfile"; + open my $fh, '>', $tmpfile + or elog(ERROR, qq{could not open the file "$tmpfile": $!}); + print $fh "Testing writing to a file\n"; + close $fh or elog(ERROR, qq{could not close the file "$tmpfile": $!}); + return 1; +$$ LANGUAGE plperl; +</programlisting> + The creation of this function will fail as its use of a forbidden + operation will be caught by the validator. + </para> + + <para> + Sometimes it is desirable to write Perl functions that are not + restricted. For example, one might want a Perl function that sends + mail. To handle these cases, PL/Perl can also be installed as an + <quote>untrusted</quote> language (usually called + <application>PL/PerlU</application><indexterm><primary>PL/PerlU</primary></indexterm>). + In this case the full Perl language is available. When installing the + language, the language name <literal>plperlu</literal> will select + the untrusted PL/Perl variant. + </para> + + <para> + The writer of a <application>PL/PerlU</application> function must take care that the function + cannot be used to do anything unwanted, since it will be able to do + anything that could be done by a user logged in as the database + administrator. Note that the database system allows only database + superusers to create functions in untrusted languages. + </para> + + <para> + If the above function was created by a superuser using the language + <literal>plperlu</literal>, execution would succeed. + </para> + + <para> + In the same way, anonymous code blocks written in Perl can use + restricted operations if the language is specified as + <literal>plperlu</literal> rather than <literal>plperl</literal>, but the caller + must be a superuser. + </para> + + <note> + <para> + While <application>PL/Perl</application> functions run in a separate Perl + interpreter for each SQL role, all <application>PL/PerlU</application> functions + executed in a given session run in a single Perl interpreter (which is + not any of the ones used for <application>PL/Perl</application> functions). + This allows <application>PL/PerlU</application> functions to share data freely, + but no communication can occur between <application>PL/Perl</application> and + <application>PL/PerlU</application> functions. + </para> + </note> + + <note> + <para> + Perl cannot support multiple interpreters within one process unless + it was built with the appropriate flags, namely either + <literal>usemultiplicity</literal> or <literal>useithreads</literal>. + (<literal>usemultiplicity</literal> is preferred unless you actually need + to use threads. For more details, see the + <citerefentry><refentrytitle>perlembed</refentrytitle></citerefentry> man page.) + If <application>PL/Perl</application> is used with a copy of Perl that was not built + this way, then it is only possible to have one Perl interpreter per + session, and so any one session can only execute either + <application>PL/PerlU</application> functions, or <application>PL/Perl</application> functions + that are all called by the same SQL role. + </para> + </note> + + </sect1> + + <sect1 id="plperl-triggers"> + <title>PL/Perl Triggers</title> + + <para> + PL/Perl can be used to write trigger functions. In a trigger function, + the hash reference <varname>$_TD</varname> contains information about the + current trigger event. <varname>$_TD</varname> is a global variable, + which gets a separate local value for each invocation of the trigger. + The fields of the <varname>$_TD</varname> hash reference are: + + <variablelist> + <varlistentry> + <term><literal>$_TD->{new}{foo}</literal></term> + <listitem> + <para> + <literal>NEW</literal> value of column <literal>foo</literal> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{old}{foo}</literal></term> + <listitem> + <para> + <literal>OLD</literal> value of column <literal>foo</literal> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{name}</literal></term> + <listitem> + <para> + Name of the trigger being called + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{event}</literal></term> + <listitem> + <para> + Trigger event: <literal>INSERT</literal>, <literal>UPDATE</literal>, + <literal>DELETE</literal>, <literal>TRUNCATE</literal>, or <literal>UNKNOWN</literal> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{when}</literal></term> + <listitem> + <para> + When the trigger was called: <literal>BEFORE</literal>, + <literal>AFTER</literal>, <literal>INSTEAD OF</literal>, or + <literal>UNKNOWN</literal> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{level}</literal></term> + <listitem> + <para> + The trigger level: <literal>ROW</literal>, <literal>STATEMENT</literal>, or <literal>UNKNOWN</literal> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{relid}</literal></term> + <listitem> + <para> + OID of the table on which the trigger fired + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{table_name}</literal></term> + <listitem> + <para> + Name of the table on which the trigger fired + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{relname}</literal></term> + <listitem> + <para> + Name of the table on which the trigger fired. This has been deprecated, + and could be removed in a future release. + Please use $_TD->{table_name} instead. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{table_schema}</literal></term> + <listitem> + <para> + Name of the schema in which the table on which the trigger fired, is + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{argc}</literal></term> + <listitem> + <para> + Number of arguments of the trigger function + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>@{$_TD->{args}}</literal></term> + <listitem> + <para> + Arguments of the trigger function. Does not exist if <literal>$_TD->{argc}</literal> is 0. + </para> + </listitem> + </varlistentry> + + </variablelist> + </para> + + <para> + Row-level triggers can return one of the following: + + <variablelist> + <varlistentry> + <term><literal>return;</literal></term> + <listitem> + <para> + Execute the operation + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>"SKIP"</literal></term> + <listitem> + <para> + Don't execute the operation + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>"MODIFY"</literal></term> + <listitem> + <para> + Indicates that the <literal>NEW</literal> row was modified by + the trigger function + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Here is an example of a trigger function, illustrating some of the + above: +<programlisting> +CREATE TABLE test ( + i int, + v varchar +); + +CREATE OR REPLACE FUNCTION valid_id() RETURNS trigger AS $$ + if (($_TD->{new}{i} >= 100) || ($_TD->{new}{i} <= 0)) { + return "SKIP"; # skip INSERT/UPDATE command + } elsif ($_TD->{new}{v} ne "immortal") { + $_TD->{new}{v} .= "(modified by trigger)"; + return "MODIFY"; # modify row and execute INSERT/UPDATE command + } else { + return; # execute INSERT/UPDATE command + } +$$ LANGUAGE plperl; + +CREATE TRIGGER test_valid_id_trig + BEFORE INSERT OR UPDATE ON test + FOR EACH ROW EXECUTE FUNCTION valid_id(); +</programlisting> + </para> + </sect1> + + <sect1 id="plperl-event-triggers"> + <title>PL/Perl Event Triggers</title> + + <para> + PL/Perl can be used to write event trigger functions. In an event trigger + function, the hash reference <varname>$_TD</varname> contains information + about the current trigger event. <varname>$_TD</varname> is a global variable, + which gets a separate local value for each invocation of the trigger. The + fields of the <varname>$_TD</varname> hash reference are: + + <variablelist> + <varlistentry> + <term><literal>$_TD->{event}</literal></term> + <listitem> + <para> + The name of the event the trigger is fired for. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>$_TD->{tag}</literal></term> + <listitem> + <para> + The command tag for which the trigger is fired. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The return value of the trigger function is ignored. + </para> + + <para> + Here is an example of an event trigger function, illustrating some of the + above: +<programlisting> +CREATE OR REPLACE FUNCTION perlsnitch() RETURNS event_trigger AS $$ + elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " "); +$$ LANGUAGE plperl; + +CREATE EVENT TRIGGER perl_a_snitch + ON ddl_command_start + EXECUTE FUNCTION perlsnitch(); +</programlisting> + </para> + </sect1> + + <sect1 id="plperl-under-the-hood"> + <title>PL/Perl Under the Hood</title> + + <sect2 id="plperl-config"> + <title>Configuration</title> + + <para> + This section lists configuration parameters that affect <application>PL/Perl</application>. + </para> + + <variablelist> + + <varlistentry id="guc-plperl-on-init" xreflabel="plperl.on_init"> + <term> + <varname>plperl.on_init</varname> (<type>string</type>) + <indexterm> + <primary><varname>plperl.on_init</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Specifies Perl code to be executed when a Perl interpreter is first + initialized, before it is specialized for use by <literal>plperl</literal> or + <literal>plperlu</literal>. + The SPI functions are not available when this code is executed. + If the code fails with an error it will abort the initialization of + the interpreter and propagate out to the calling query, causing the + current transaction or subtransaction to be aborted. + </para> + <para> + The Perl code is limited to a single string. Longer code can be placed + into a module and loaded by the <literal>on_init</literal> string. + Examples: +<programlisting> +plperl.on_init = 'require "plperlinit.pl"' +plperl.on_init = 'use lib "/my/app"; use MyApp::PgInit;' +</programlisting> + </para> + <para> + Any modules loaded by <literal>plperl.on_init</literal>, either directly or + indirectly, will be available for use by <literal>plperl</literal>. This may + create a security risk. To see what modules have been loaded you can use: +<programlisting> +DO 'elog(WARNING, join ", ", sort keys %INC)' LANGUAGE plperl; +</programlisting> + </para> + <para> + Initialization will happen in the postmaster if the <literal>plperl</literal> library is + included in <xref linkend="guc-shared-preload-libraries"/>, in which + case extra consideration should be given to the risk of destabilizing + the postmaster. The principal reason for making use of this feature + is that Perl modules loaded by <literal>plperl.on_init</literal> need be + loaded only at postmaster start, and will be instantly available + without loading overhead in individual database sessions. However, + keep in mind that the overhead is avoided only for the first Perl + interpreter used by a database session — either PL/PerlU, or + PL/Perl for the first SQL role that calls a PL/Perl function. Any + additional Perl interpreters created in a database session will have + to execute <literal>plperl.on_init</literal> afresh. Also, on Windows there + will be no savings whatsoever from preloading, since the Perl + interpreter created in the postmaster process does not propagate to + child processes. + </para> + <para> + This parameter can only be set in the <filename>postgresql.conf</filename> file or on the server command line. + </para> + </listitem> + </varlistentry> + + <varlistentry id="guc-plperl-on-plperl-init" xreflabel="plperl.on_plperl_init"> + <term> + <varname>plperl.on_plperl_init</varname> (<type>string</type>) + <indexterm> + <primary><varname>plperl.on_plperl_init</varname> configuration parameter</primary> + </indexterm> + </term> + <term> + <varname>plperl.on_plperlu_init</varname> (<type>string</type>) + <indexterm> + <primary><varname>plperl.on_plperlu_init</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + These parameters specify Perl code to be executed when a Perl + interpreter is specialized for <literal>plperl</literal> or + <literal>plperlu</literal> respectively. This will happen when a PL/Perl or + PL/PerlU function is first executed in a database session, or when + an additional interpreter has to be created because the other language + is called or a PL/Perl function is called by a new SQL role. This + follows any initialization done by <literal>plperl.on_init</literal>. + The SPI functions are not available when this code is executed. + The Perl code in <literal>plperl.on_plperl_init</literal> is executed after + <quote>locking down</quote> the interpreter, and thus it can only perform + trusted operations. + </para> + <para> + If the code fails with an error it will abort the initialization and + propagate out to the calling query, causing the current transaction or + subtransaction to be aborted. Any actions already done within Perl + won't be undone; however, that interpreter won't be used again. + If the language is used again the initialization will be attempted + again within a fresh Perl interpreter. + </para> + <para> + Only superusers can change these settings. Although these settings + can be changed within a session, such changes will not affect Perl + interpreters that have already been used to execute functions. + </para> + </listitem> + </varlistentry> + + <varlistentry id="guc-plperl-use-strict" xreflabel="plperl.use_strict"> + <term> + <varname>plperl.use_strict</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>plperl.use_strict</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + When set true subsequent compilations of PL/Perl functions will have + the <literal>strict</literal> pragma enabled. This parameter does not affect + functions already compiled in the current session. + </para> + </listitem> + </varlistentry> + + </variablelist> +</sect2> + + <sect2 id="plperl-missing"> + <title>Limitations and Missing Features</title> + + <para> + The following features are currently missing from PL/Perl, but they + would make welcome contributions. + + <itemizedlist> + <listitem> + <para> + PL/Perl functions cannot call each other directly. + </para> + </listitem> + + <listitem> + <para> + SPI is not yet fully implemented. + </para> + </listitem> + + <listitem> + <para> + If you are fetching very large data sets using + <literal>spi_exec_query</literal>, you should be aware that + these will all go into memory. You can avoid this by using + <literal>spi_query</literal>/<literal>spi_fetchrow</literal> as + illustrated earlier. + </para> + <para> + A similar problem occurs if a set-returning function passes a + large set of rows back to PostgreSQL via <literal>return</literal>. You + can avoid this problem too by instead using + <literal>return_next</literal> for each row returned, as shown + previously. + </para> + </listitem> + + <listitem> + <para> + When a session ends normally, not due to a fatal error, any + <literal>END</literal> blocks that have been defined are executed. + Currently no other actions are performed. Specifically, + file handles are not automatically flushed and objects are + not automatically destroyed. + </para> + </listitem> + </itemizedlist> + </para> + </sect2> + + </sect1> + +</chapter> |