When running the deployer, you must use a
classpath that can see the deploy.jar found in the
Pl/Java distribution and the postgresql.jar from the PostgreSQL distribution. The former contains the code for
the deployer command and the second includes the PostgreSQL JDBC driver. You then run the deployer with the command:
java –cp <your
classpath> org.postgresql.pljava.deploy.Deployer [ options ]
It’s recommended that create a shell script or a .bat script that does this
for you so that you don’t have to do this over and over again.
-install |
Installs the Java language along with the sqlj procedures. The deployer
will fail if the language is installed already. |
-reinstall |
Reinstalls the Java language and the sqlj
procedures. This will effectively drop all jar files that have been loaded. |
-remove |
Drops the Java language and the sqjl procedures
and loaded jars. |
-user <user name> |
Name of user that connects to the database. Default is ”postgres” |
-password <password> |
Password of user that connects to the database. Default is no password. |
-database <database> |
The name of the database to connect to. Default is ”postgres”. |
-host <hostname> |
Name of the host. Default is ”localhost”. |
-windows |
Use this option if the host runs on a windows platform. Affects the name
used for the Pl/Java dynamic library. |
The install_jar command loads a jarfile from a location appointed by an URL into the SQLJ
jar repository. It is an error if a jar with the given name already exists in
the repository.
SELECT
sqlj.install_jar(<jar_name>, <jar_url>, <deploy>);
jar_name This is the name by which this jar can
be referenced once it has been loaded.
jar_url The URL that denotes the location
of the jar that should be loaded.
deploy True if the jar should be deployed according to a
deployment descriptor, false otherwise. This option is currently ignored.
The replace_jar will replace a loaded jar with
another jar. Use this command to update already loaded files. It’s an error if
the jar is not found.
SELECT sqlj.replace_jar(<jar_name>,
<jar_url>, <redeploy>);
jar_name The name of the jar to be replaced.
jar_url The URL that denotes the location
of the jar that should be loaded.
redeploy True if the jar should be undeployed
and deployed according to a deployment descriptor, false otherwise. This option
is currently ignored.
The remove_jar will drop the jar from the jar
repository. Any classpath that references this jar will be updated accordingly.
It’s an error if the jar is not found.
SELECT sqlj.remove_jar(<jar_name>,
<undeploy>);
jar_name The name of the jar to be removed.
undeploy True if the jar should be undeployed according to a deployment descriptor, false
otherwise. This option is currently ignored.
The get_classpath will return the classpath that
has been defined for the given schema or NULL if the schema has no classpath.
It’s an error if the given schema does not exist.
SELECT sqlj.get_classpath(<schema>);
schema The name of the schema.
The set_classpath will define a classpath for the
given schema. A classpath consists of a colon separated list of jar names. It’s
an error if the given schema does not exist or if one or more jar names
references non existent jars.
SELECT sqlj.set_classpath(<schema>,
<classpath>);
schema The name of the schema.
classpath The colon separated
list of jar names.
A Java function is declared with the name of a class and a static method on
that class. The class will be resolved using the classpath that has been
defined for the schema where the function is declared. If no classpath has been
defined for that schema, the ”public” schema is used.
If no classpath is found there either, the class is resolved using the system classloader.
The following function can be declared to access the static method getProperty on java.lang.System
class:
CREATE FUNCTION getsysprop(VARCHAR)
RETURNS VARCHAR
AS ’java.lang.System.getProperty’
LANGUAGE java;
SELECT getsysprop(’user.home’);
Scalar types are mapped in a straight forward way. Here’s a table of the
current mappings (will be updated as more mappings are implemented).
PostgreSQL Java
bool boolean
’char’ byte
int2 short
int4 int
int8 long
float4 float
float8 double
varchar java.lang.String
text java.lang.String
bytea byte[]
date java.sql.Date
time java.sql.Time
(stored
value treated as local time)
timetz java.sql.Time
timestamp java.sql.Timestamp
(stored
value treated as local time)
timestamptz java.sql.Timestamp
complex java.sql.ResultSet
setof complex java.sql.ResultSet
All other types are currently mapped to java.lang.String
and will utilize the standard textin/textout routines registered for respective type.
The scalar types that map to Java primitives can not be passed as NULL
values. To enable this, those types can have an alternative mapping. You enable
this mapping by explicitly denoting it in the method reference.
CREATE FUNCTION trueIfEvenOrNull(integer)
RETURNS bool
AS ’foo.fee.Fum.trueIfEvenOrNull(java.lang.Integer)’
LANGUAGE java;
In java, you would have something like:
package foo.fee;
public class Fum
{
static boolean trueIfEvenOrNull(Integer
value)
{
return
(value == null)
? true
: (value.intValue() % 1) == 0;
}
}
The following two statements should both yield true:
SELECT trueIfEvenOrNull(NULL);
SELECT trueIfEvenOrNull(4);
In order to return NULL values from a Java method, you simply use the object
type that corresponds to the primitive (i.e. you return java.lang.Integer
instead of int). The Pl/Java resolve mechanism will
find the method regardless. Since Java cannot have different return types for
methods with the same name, this does not introduce any ambiguity.
A complex type will always be passed as a read-only java.sql.ResultSet
with exaclty one row. The ResultSet
will be positioned on its row so no call to next should be made. The values of
the complex type are retrieved using the standard getter methods of the ResultSet.
Example:
CREATE TYPE complexTest
AS(base integer,
incbase integer, ctime timestamptz)
;
CREATE FUNCTION useComplexTest(complexTest)
RETURNS VARCHAR
AS 'foo.fee.Fum.useComplexTest'
IMMUTABLE LANGUAGE java;
In class Fum we add the static following static
method:
public static String useComplexTest(ResultSet
complexTest)
throws SQLException
{
int base = complexTest.getInt(1);
int incbase = complexTest.getInt(2);
Timestamp ctime =
complexTest.getTimestamp(3);
return "Base
= \"" + base +
"\",
incbase = \"" + incbase +
"\",
ctime = \"" + ctime + "\"";
}
Java does not stipulate any way to create a ResultSet
from scratch. Hence, returning a ResultSet is not an
option. The SQL-2003 draft suggest that a complex
return value instead is handled as an IN/OUT parameter and Pl/Java implements
it that way. If you declare a function that returns a complex type, you will
need to use a Java method with boolean
return type and whos last parameter is a java.sql.ResultSet. The parameter will be initialized to an
empty updateable ResultSet that contains exactly one row.
Assume that we still have the complexTest type
created above.
CREATE FUNCTION createComplexTest(int, int)
RETURNS
complexTest
AS 'foo.fee.Fum.createComplexTest'
IMMUTABLE LANGUAGE java;
The Pl/Java method resolve will now find the following method in the Fum class:
public static boolean
complexReturn(int
base, int increment, ResultSet receiver)
throws SQLException
{
receiver.updateInt(1,
base);
receiver.updateInt(2,
base + increment);
receiver.updateTimestamp(3,
new
Timestamp(System.currentTimeMillis()));
return true;
}
The return value denotes if the receiver should be considered as a valid tuple (true) or NULL (false).
Returning sets are tricky. You don’t want to first build a set and then
return it since large sets would eat too much resources. Its
far better to produce one row at a time. Incidentally, that’s exactly what the PostgreSQL backend expects a function with SETOF return to
do. To accomplish this, a new interface org.postgresql.ResultSetProvider
was invented. It contains one method that takes a java.sql.ResultSet
and an int as its parameters. The ResultSet
works the same way as for function returning complex types, the int reflects the row count.
You can use this interface the following way (here I let the Fum class itself implement the interface,
it could be another class of course).
CREATE FUNCTION javatest.listComplexTests(int, int)
RETURNS
SETOF complexTest
AS 'foo.fee.Fum.listComplexTest'
IMMUTABLE LANGUAGE java;
Now we need a static java method that returns an instance that implements
the ResultSetProvider interface.
package foo.fee;
import org.postgresql.pljava.ResultSetProvider;
public class Fum implements ResultSetProvider
{
private final int
m_base;
private final int
m_increment;
public
Fum(int base, int increment)
{
m_base = base;
m_increment = increment;
}
public boolean
assignRowValues(ResultSet receiver, int currentRow)
throws SQLException
{
// Stop when we
reach 12 rows.
//
if(currentRow
>= 12)
return false;
receiver.updateInt(1,
m_base);
receiver.updateInt(2,
m_base + m_increment * currentRow);
receiver.updateTimestamp(3,
new Timestamp(System.currentTimeMillis()));
return true;
}
public static
ResultSetProvider listComplexTests(int base, int increment)
throws SQLException
{
return new Fum(base,
increment);
}
}
The listComplextTests
method is called once. It may return NULL
if no results are available or an instance
of the ResultSetProvider
.
Here the Fum
implements this interface so it returns an instance of itself. The method assignRowValues
will
then be called repeatedly until it returns false.
The method signature of a trigger is predefined. A trigger method must
always return void
and have a org.postgresql.pljava.TriggerData
parameter. No
more, no less. The TriggerData
interface provides access to two ResultSet
instances; one representing the old
row and one representing the new. The old row is read-only, the new row is
updateable.
The sets are only available for triggers that are fired ON EACH ROW
. Delete
triggers have no new row, and insert triggers have no old row. Only update
triggers have both.
In addition to the sets, several boolean
methods exists to gain more information about the trigger. Here’s an example
trigger:
CREATE TABLE mdt (
id int4,
idesc text,
moddate timestamp
DEFAULT CURRENT_TIMESTAMP NOT NULL);
CREATE FUNCTION moddatetime()
RETURNS trigger
AS
'org.postgresql.pljava.example.Triggers.moddatetime'
LANGUAGE java";
CREATE TRIGGER mdt_moddatetime
BEFORE UPDATE ON mdt
FOR EACH ROW
EXECUTE PROCEDURE moddatetime (moddate);
The Java method in class
org.postgresql.pljava.example.Triggers
looks like this:
/**
* Update a modification time when the row is updated.
*/
static void
moddatetime(TriggerData td)
throws SQLException
{
if(td.isFiredForStatement())
throw new
TriggerException(td, "can't process STATEMENT events");
if(td.isFiredAfter())
throw new
TriggerException(td, "must be fired before event");
if(!td.isFiredByUpdate())
throw new
TriggerException(td, "can only process UPDATE events");
ResultSet _new =
td.getNew();
String[] args =
td.getArguments();
if(args.length !=
1)
throw new
TriggerException(td, "one argument was expected");
_new.updateTimestamp(args[0],
new Timestamp(System.currentTimeMillis()));
}
Pl/Java uses the standard Java 1.4 Logger. Hence, you can write things like:
Logger.getAnonymousLogger().info(
"Time is
" +
new Date(System.currentTimeMillis()));
At present, the logger is hardwired to a handler that maps the current state
of the PostgreSQL configuration setting log_min_messages
to a valid Logger level and that
outputs all messages using the backend function elog
().