Using PL/Java to Return SETOF RECORD
PL/Java allows a quite easy implementation of result set providers, objects that will produce rows that can be used as tables in queries. In order to produce a result set, the main steps are: 1) implement theResultSetProvider
interface and its method to effectively produce the data;
2) build a PL/Java function that will instantiate the above ResultsetProvider
, so that PL/Java will wrap such function into a RETURN SETOF RECORD
SQL function.
In the following there is a quite simple demostration about the production of records from PL/Java.
Implementing the ResultSetProvider
PL/Java has the ResultSetProvider
interface that requires the implementation of two methods:
assignRowValues
that is called for every row in the result set, and must returntrue
to indicate that a new row has been added to the result set, orfalse
to indicate that the result set is complete and no more rows will be added;close
that is called when the result set is closed byassignRowValue
.
assignRowValues
function accepts two arguments:
- an
ResultSet
object that is the container for all the rows; - a
long
value indicating the current row for which the method has been called. This counter starts at zero, as in normal Java list/array manipulations, not as in SQL.
public class Task1 implements ResultSetProvider {
private final static Logger logger = Logger.getAnonymousLogger();
public Task1( int maxRows ) {
super();
this.maxRows = maxRows;
}
private int maxRows = 10;
@Override
public boolean assignRowValues( ResultSet rs, int row )
throws SQLException {
if ( row > maxRows )
return false;
logger.info( String.format( "Producing row %d/%d", row, maxRows ) );
rs.updateString( 1, String.format( "Row %d out of %d from %s",
row,
maxRows,
this.getClass().getName() ) );
rs.updateInt( 2, row );
rs.updateInt( 3, maxRows );
rs.updateDate( 4, new java.sql.Date( Calendar.getInstance().getTimeInMillis() ) );
return true;
}
@Override
public void close() {
logger.info( "Closing resultset" );
}
}
The
assignRowValues
function simply adds to the ResultSet
a string field, two integers and one date field.
The production of the result set ends as soon as the produced rows count as in maxRows
parameter, that is decided when the class is instantiated.
Creating a function to call the producer
It is possible to create a PL/Java function that will instantiate the aboce class, returning it. In order for PL/Java to understand that the function will produce a result set, the function must return aResultSetProvider
.
@Function( onNullInput = RETURNS_NULL, effects = IMMUTABLE )
public static final ResultSetProvider rs_producer_pljava() throws SQLException {
logger.log( Level.INFO, "Entering rs_producer_pljava" );
Task1 producer = new Task1( 20 );
return producer;
}
Once the function has been compiled, and the JAR installed, there will be a function defined as:
testdb=> \sf rs_producer_pljava
CREATE OR REPLACE FUNCTION public.rs_producer_pljava()
RETURNS SETOF record
LANGUAGE java
IMMUTABLE STRICT
AS $function$PWC256.Task1.rs_producer_pljava()$function$
Note how the function has been produced as
RETURN SETOF RECORD
and will call the PL(Java function, that in turn will instantiate the ResultSetProviderr
.
Using the function
It is now possible to query the function from SQL:testdb=> select j.* from rs_producer_pljava() as j(t text, r int, m int, d date);
INFO: PWC256.Task1 Entering rs_producer_pljava
INFO: PWC256.Task1 Producing row 0/20
INFO: PWC256.Task1 Producing row 1/20
INFO: PWC256.Task1 Producing row 2/20
...
INFO: PWC256.Task1 Producing row 18/20
INFO: PWC256.Task1 Producing row 19/20
INFO: PWC256.Task1 Producing row 20/20
INFO: PWC256.Task1 Closing resultset
t | r | m | d
-----------------------------------+---+----+------------
Row 0 out of 20 from PWC256.Task1 | 0 | 20 | 2024-02-07
Row 1 out of 20 from PWC256.Task1 | 1 | 20 | 2024-02-07
Row 2 out of 20 from PWC256.Task1 | 2 | 20 | 2024-02-07
Row 3 out of 20 from PWC256.Task1 | 3 | 20 | 2024-02-07
Row 4 out of 20 from PWC256.Task1 | 4 | 20 | 2024-02-07
...
From the log messages it is possible to see that the result is being used to produce the records, and at the end it is closed.
Passing dynamically the number of rows to produce
What if there is the need to decide dynamically how many rows theResultSetProvider
has to produce?
It simply requires to change the PL/Java function passing an integer argument:
@Function( onNullInput = RETURNS_NULL, effects = IMMUTABLE )
public static final ResultSetProvider rs_producer_pljava( int howManyRows ) throws SQLException {
logger.log( Level.INFO, "Entering rs_producer_pljava" );
if ( howManyRows <= 0 )
howManyRows = 5;
Task1 producer = new Task1( howManyRows );
return producer;
}
And it is then possible to query the function with the following query:
testdb=> select j.* from rs_producer_pljava( 3 ) as j(t text, r int, m int, d date);
INFO: PWC256.Task1 Entering rs_producer_pljava
INFO: PWC256.Task1 Producing row 0/3
INFO: PWC256.Task1 Producing row 1/3
INFO: PWC256.Task1 Producing row 2/3
INFO: PWC256.Task1 Producing row 3/3
INFO: PWC256.Task1 Closing resultset
t | r | m | d
----------------------------------+---+---+------------
Row 0 out of 3 from PWC256.Task1 | 0 | 3 | 2024-02-07
Row 1 out of 3 from PWC256.Task1 | 1 | 3 | 2024-02-07
Row 2 out of 3 from PWC256.Task1 | 2 | 3 | 2024-02-07
(2 rows)