43.1. Funciones y argumentos de PL/Perl #

Para crear una función en el lenguaje PL/Perl, utiliza la sintaxis estándar de CREATE FUNCTION:

CREATE FUNCTION funcname (argument-types)
RETURNS return-type
-- los atributos de la función pueden ir aquí
AS $$
    # el cuerpo de la función PL/Perl va aquí
$$ LANGUAGE plperl;

El cuerpo de la función es código Perl ordinario. De hecho, el código de acoplamiento de PL/Perl lo envuelve dentro de una subrutina Perl. Una función PL/Perl se llama en un contexto escalar, por lo que no puede devolver una lista. Puedes devolver valores no escalares (arrays, registros y conjuntos) devolviendo una referencia, como se explica a continuación.

En un procedimiento PL/Perl, cualquier valor de retorno del código Perl es ignorado.

PL/Perl también admite bloques de código anónimos llamados con la sentencia DO:

DO $$
    # código PL/Perl
$$ LANGUAGE plperl;

Un bloque de código anónimo no recibe argumentos, y cualquier valor que pueda devolver es descartado. Por lo demás, se comporta igual que una función.

Note

El uso de subrutinas anidadas nombradas es peligroso en Perl, especialmente si hacen referencia a variables léxicas en el ámbito contenedor. Debido a que una función PL/Perl está envuelta en una subrutina, cualquier subrutina nombrada que coloques dentro de ella estará anidada. En general, es mucho más seguro crear subrutinas anónimas que llames a través de un coderef. Para obtener más información, consulta las entradas para Variable "%s" will not stay shared y Variable "%s" is not available en la página de manual de perldiag, o busca en Internet perl nested named subroutine.

La sintaxis del comando CREATE FUNCTION requiere que el cuerpo de la función se escriba como una constante de cadena. Normalmente es más conveniente utilizar el entrecomillado de dólar (dollar quoting) (consulta la Section 4.1.2.4) para la constante de cadena. Si decides utilizar la sintaxis de cadena de escape E'', debes duplicar las comillas simples (') y barras invertidas (\) utilizadas en el cuerpo de la función (consulta la Section 4.1.2.1).

Los argumentos y resultados se manejan como en cualquier otra subrutina de Perl: los argumentos se pasan en @_, y el valor del resultado se devuelve con return or como la última expresión evaluada en la función.

Por ejemplo, una función que devuelva el mayor de dos valores enteros podría definirse como:

CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    if ($_[0] > $_[1]) { return $_[0]; }
    return $_[1];
$$ LANGUAGE plperl;

Note

Los argumentos se convertirán de la codificación de la base de datos a UTF-8 para su uso dentro de PL/Perl, y luego se convertirán de UTF-8 de regreso a la codificación de la base de datos al retornar.

Si se pasa un valor nulo de SQL a una función, el valor del argumento aparecerá como indefinido (undefined) en Perl. La definición de la función anterior no se comportará muy bien con entradas nulas (de hecho, actuará como si fueran ceros). Podríamos añadir STRICT a la definición de la función para hacer que PostgreSQL haga algo más razonable: si se pasa un valor nulo, la función no se llamará en absoluto, sino que simplemente devolverá un resultado nulo automáticamente. Alternativamente, podríamos comprobar las entradas no definidas en el cuerpo de la función. Por ejemplo, supongamos que quisiéramos que perl_max con un argumento nulo y otro no nulo devolviera el argumento no nulo, en lugar de un valor nulo:

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;

Como se muestra arriba, para devolver un valor nulo de SQL desde una función PL/Perl, devuelve un valor indefinido. Esto se puede hacer tanto si la función es estricta (strict) como si no lo es.

Cualquier cosa en un argumento de función que no sea una referencia es una cadena, la cual está en la representación de texto externa estándar de PostgreSQL para el tipo de datos correspondiente. En el caso de los tipos numéricos o de texto ordinarios, Perl simplemente hará lo correcto y el programador normalmente no tendrá que preocuparse por ello. Sin embargo, en otros casos el argumento necesitará ser convertido a una forma que sea más utilizable en Perl. Por ejemplo, la función decode_bytea se puede utilizar para convertir un argumento de tipo bytea en binario sin escapar.

De manera similar, los valores devueltos a PostgreSQL deben estar en el formato de representación de texto externa. Por ejemplo, la función encode_bytea se puede utilizar para escapar datos binarios para un valor de retorno de tipo bytea.

Un caso que es particularmente importante son los valores booleanos. Como se acaba de mencionar, el comportamiento por defecto para los valores bool es que se pasan a Perl como texto, por lo tanto, ya sea 't' o 'f'. Esto es problemático, ya que Perl no tratará 'f' como falso. Es posible mejorar esto utilizando una transformación (transform) (consulta la CREATE TRANSFORM). Las transformaciones adecuadas son proporcionadas por la extensión bool_plperl. Para usarla, instala la extensión:

CREATE EXTENSION bool_plperl;  -- o bool_plperlu para PL/PerlU

Luego, utiliza el atributo de función TRANSFORM para una función PL/Perl que reciba o devuelva bool, por ejemplo:

CREATE FUNCTION perl_and(bool, bool) RETURNS bool
TRANSFORM FOR TYPE bool
AS $$
  my ($a, $b) = @_;
  return $a && $b;
$$ LANGUAGE plperl;

Cuando se aplica esta transformación, los argumentos bool serán vistos por Perl como 1 o vacío, es decir, propiamente verdaderos o falsos. Si el resultado de la función es de tipo bool, será verdadero o falso según si Perl evaluaría el valor devuelto como verdadero. Transformaciones similares también se realizan para los argumentos de consulta booleanos y los resultados de las consultas SPI realizadas dentro de la función (Section 43.3.1).

Perl puede devolver arrays de PostgreSQL como referencias a arrays de Perl. Aquí hay un ejemplo:

CREATE OR REPLACE function returns_array()
RETURNS text[][] AS $$
    return [['a"b','c,d'],['e\\f','g']];
$$ LANGUAGE plperl;

select returns_array();

Perl pasa los arrays de PostgreSQL como un objeto bendecido (blessed) PostgreSQL::InServer::ARRAY. Este objeto puede ser tratado como una referencia a un array o como una cadena, lo que permite la compatibilidad hacia atrás con el código Perl escrito para versiones de PostgreSQL inferiores a la 9.1. Por ejemplo:

CREATE OR REPLACE FUNCTION concat_array_elements(text[]) RETURNS TEXT AS $$
    my $arg = shift;
    my $result = "";
    return undef if (!defined $arg);

    # como una referencia a un array
    for (@$arg) {
        $result .= $_;
    }

    # también funciona como una cadena
    $result .= $arg;

    return $result;
$$ LANGUAGE plperl;

SELECT concat_array_elements(ARRAY['PL','/','Perl']);

Note

Los arrays multidimensionales se representan como referencias a arrays de referencias de menor dimensión de una manera común para cualquier programador de Perl.

Los argumentos de tipo compuesto se pasan a la función como referencias a hashes. Las claves del hash son los nombres de los atributos del tipo compuesto. Aquí hay un ejemplo:

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;

Una función PL/Perl puede devolver un resultado de tipo compuesto utilizando el mismo enfoque: devolver una referencia a un hash que tenga los atributos requeridos. Por ejemplo:

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();

Cualquier columna en el tipo de datos de resultado declarado que no esté presente en el hash se devolverá como valor nulo.

De manera similar, los argumentos de salida de los procedimientos se pueden devolver como una referencia a un hash:

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);

Las funciones PL/Perl también pueden devolver conjuntos (sets) de tipos escalares o compuestos. Normalmente querrás devolver las filas una a la vez, tanto para acelerar el tiempo de inicio como para evitar acumular todo el conjunto de resultados en memoria. Puedes hacer esto con return_next como se ilustra a continuación. Ten en cuenta que después del último return_next, debes poner ya sea return o (mejor) return undef.

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;

Para conjuntos de resultados pequeños, puedes devolver una referencia a un array que contenga escalares, referencias a arrays o referencias a hashes para tipos simples, tipos de array y tipos compuestos, respectivamente. Aquí hay algunos ejemplos sencillos de devolución de todo el conjunto de resultados como una referencia a un array:

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();

Si deseas utilizar la directiva (pragma) strict con tu código, tienes algunas opciones. Para uso global temporal puedes ejecutar SET plperl.use_strict en true. Esto afectará a las compilaciones posteriores de funciones PL/Perl, pero no a las funciones ya compiladas en la sesión actual. Para uso global permanente puedes establecer plperl.use_strict en true en el archivo postgresql.conf.

Para un uso permanente en funciones específicas, simplemente puedes poner:

use strict;

en la parte superior del cuerpo de la función.

La directiva feature también está disponible para use si tu versión de Perl es 5.10.0 o superior.