Pl/Java Userguide

Table of contents

 

Pl/Java Userguide 1

Table of contents 1

Utilities 1

Deployer 1

Deployer options 2

SQJL functions 2

install_jar 2

Usage 2

Parameters 2

replace_jar 3

Usage 3

Parameters 3

remove_jar 3

Usage 3

Parameters 3

get_classpath 3

Usage 3

Parameters 3

set_classpath 3

Usage 4

Parameters 4

Writing Java functions 4

SQL declaration 4

Type mapping 4

NULL handling 5

Complex types 5

Returning complex types 6

Functions returning sets 6

Triggers 7

Logging 8

Utilities

Deployer

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.

 

Deployer options

-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.

 

SQJL functions

install_jar

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.

Usage

SELECT sqlj.install_jar(<jar_name>, <jar_url>,  <deploy>);

Parameters

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.

 

replace_jar

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.

Usage

SELECT sqlj.replace_jar(<jar_name>, <jar_url>,  <redeploy>);

Parameters

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.

 

remove_jar

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.

Usage

SELECT sqlj.remove_jar(<jar_name>, <undeploy>);

Parameters

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.

 

get_classpath

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.

Usage

SELECT sqlj.get_classpath(<schema>);

Parameters

schema            The name of the schema.

 

set_classpath

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.

Usage

SELECT sqlj.set_classpath(<schema>, <classpath>);

Parameters

schema            The name of the schema.

classpath         The colon separated list of jar names.

 

 

Writing Java functions

SQL declaration

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

Type mapping

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.

NULL handling

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.

Complex types

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 + "\"";

   }

Returning complex types

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

Functions returning sets

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.

Triggers

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

   }

Logging

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