Invoking (your own) Perl from PL/Perl
Invoking your own Perl code from PL/Perl, how hard can it be?Well, it turns out that it can be harder than you think. PL/Perl is made to allow Perl interacting with the SQL world. Assume a function, named
get_prime
requires to invoke another Perl function is_prime
to test if a number is prime or not.
How is it possible to chain the function invocation?
Invoking PL/Perl from PL/Perl via a query
One obvious possibility is to wrapis_prime
into a PL/Perl function.
Since a PL/Perl function is, well, an ordinary function, it is always possible to call it the SQL way, as another ordinary function.
CREATE OR REPLACE FUNCTION is_prime( int )
RETURNS bool AS $CODE$
my ( $n ) = @_;
for ( 2 .. $n ) {
last if $_ >= ( $n / 2 ) + 1;
return 0 if $n % $_ == 0;
}
return 1;
$CODE$ LANGUAGE plperl;
CREATE OR REPLACE FUNCTION get_primes( int )
RETURNS SETOF int AS $CODE$
for ( 1 .. $_[ 0 ] ) {
my $result_set = spi_exec_query( "SELECT is_prime( $_ )" );
return_next( $_ ) if ( $result_set->{ rows }[0]->{ is_prime } eq 't' );
}
return undef;
$CODE$ LANGUAGE plperl;
The function
get_primes
builds a query (SELECT is_prime( $_ );
) that is executed several times in order to get the result.
Advantages of this approach are that this is the natural way to query PostgreSQL functions, and therefore it would be possible to mix and match PL/Perl functions with other PL-functions. The main drawback is that this approach is tedious and error prone, since there is the need to build SQL queries. Moreover, handling invocation and argument passing will slow down the execution of the main function.
Using anonymous subroutines
Luckily, Perl allows the definition of a subroutine within another subroutine, and to call it when required. One way to achieve this is by code references.CREATE OR REPLACE FUNCTION get_prime( int )
RETURNS SETOF int AS $CODE$
my $is_prime = sub {
my ( $n ) = @_;
for ( 2 .. $n ) {
last if $_ >= ( $n / 2 ) + 1;
return 0 if $n % $_ == 0;
}
return 1;
};
for ( 1 .. $_[ 0 ] ) {
return_next( $_ ) if $is_prime->( $_ );
}
return undef;
$CODE$ LANGUAGE plperl;
This makes very easy and natural to invoke Perl code (in this example, the
$is_prime
function) within PL/Perl.
The main advantage of this approach is that it is all written in pure Perl. The main drawback is that the $is_prime
function is now private to the scope of the get_prime
function, and therefore cannot be reused by other functions.
Injecting a line of code at PL/Perl boot
PostgreSQL provides a set ofplperl
GUCs that allow you to set different properties of the Perl enrivornment.
One possibility, is to pre-declare a function so that once the PL/Perl engine will run, the function will be there.
Unluckily, GUCs do not allow for a setting to be split on different lines. Luckily, Perl is not Python, so you can write your own code in a single line.
# in postgresql.conf
plperl.on_plperl_init = 'sub is_prime { ... }'
Therefore, when the PL/Perl engine starts, it gets the
is_prime
sub defined for free. This means that get_rpimes
can be simply written as:
CREATE OR REPLACE FUNCTION get_primes( int )
RETURNS SETOF int AS $CODE$
return [ grep { is_prime( $_ ) } ( 2 .. $_[ 0 ] ) ];
$CODE$ LANGUAGE plperl;
The main advantage of this approach is that it is simple. The main drawback is that it is complex, too. Writing code within a single line is not a good habit. Most notably, this makes the code declared into
plperl.on_plperl_init
available to every instance of the PL/Perl engine, and this is a security risk!
Injecting a module at PL/Perl boot
Following a similar approach, it is possible to place your custom code into a module and make PL/Perluse
such module before the execution starts.
The first step required is to provide a Perl module and place it where PostgreSQL can find on the filesystem.
# $PGDATA/conf.d/fluca1978.pm
sub is_prime {
my ( $n ) = @_;
for ( 2 .. $n ) {
last if $_ >= ( $n / 2 ) + 1;
return 0 if $n % $_ == 0;
}
return 1;
}
1;
Now, it is possible to load the module either in
plperl.on_init
or in plperl.on_plperlu_init
:
plperl.on_init = 'use lib q{/postgres/15/data/conf.f/fluca1978.pm}; use fluca1978;
Last, since the module has been loaded for every PL/Perl engine, the
get_primes
function remains as simple as:
CREATE OR REPLACE FUNCTION get_primes( int )
RETURNS SETOF int AS $CODE$
return [ grep { is_prime( $_ ) } ( 2 .. $_[ 0 ] ) ];
$CODE$ LANGUAGE plperl;
In the case
plperl.on_init
does not include the use
statement, the function get_primes
should have been defined as plperlu
loading the module itself.
The main advantage of this approach is that it provides modularity of available code.
The main drawback, as for the previous approach, is that it injects code into every PL/Perl engine that is going to be started.
Using shared code
PL/Perl provides the%_SHARED
hash that is shared among functions running within the same connections and with the same user.
This allows for storing an anonymous subroutine into the %_SHARED
object and use it later.
The first step is to initialize the hash with the anonymous subroutine:
CREATE OR REPLACE FUNCTION my_plperl_init()
RETURNS VOID AS $CODE$
$_SHARED{ is_prime } = sub {
my ( $n ) = @_;
for ( 2 .. $n ) {
last if $_ >= ( $n / 2 ) + 1;
return 0 if $n % $_ == 0;
}
return 1;
};
$CODE$ LANGUAGE plperl;
Then, it is possible to make
get_primes
to use the shared reference:
CREATE OR REPLACE FUNCTION get_primes( int )
RETURNS SETOF int AS $CODE$
return [ grep { $_SHARED{ is_prime }->( $_ ) } ( 2 .. $_[ 0 ] ) ];
$CODE$ LANGUAGE plperl;
The main adavantage of this approach is that it does not require a full code injection: only sessions that require the code to be shared will use it. The main drawback is that there is no real sharing of code, it is just temporary code living in the session space. Moreover, this approach requires an initialization phase, that can be error prone.
Conclusions
Being PL/Perl, well, Perl, there’s more than one way to do it!Depending on the aims and constraints, there are different ways to invoke Perl code from other PL/Perl code. The main considerations, when choosing an approach or another, are related to code reusability and performances. Wrapping Perl into PL/Perl provides for better code reusability, but requires more resources and code bloating. Using a pure Perl approach provides for the best performances and code readibility, but can open the door to some security risks.