H.6. pljava — добавление хранимых процедур, триггеров и функций, написанных на Java, в Postgres Pro #
- H.6.1. Описание
- H.6.2. Установка
- H.6.3. Дескриптор развёртывания SQLJ
- H.6.4. Сопоставление функций
- H.6.5. Триггеры
- H.6.6. Сопоставление типов по умолчанию
- H.6.7. Сопоставление SQL-типа с Java-классом
- H.6.8. Создание скалярного пользовательского типа
- H.6.9. Возврат сложных типов
- H.6.10. Функции, возвращающие множества
- H.6.11. Использование JDBC
- H.6.12. Обработка исключений
- H.6.13. Точки сохранения
- H.6.14. Протоколирование
- H.6.15. Функции SQLJ
- H.6.16. Параметры конфигурации
- H.6.2. Установка
H.6.1. Описание #
Модуль pljava позволяет писать хранимые процедуры, триггеры и функции на языке Java и выполнять их обслуживающим процессом Postgres Pro.
pljava предоставляет следующие основные возможности:
Возможность писать функции, триггеры и пользовательские типы, используя последние версии Java.
Стандартизированные утилиты для установки и поддержания Java-кода в базе данных.
Стандартизированные сопоставления параметров и результатов. Поддержка скалярных и составных пользовательских типов (user-defined types, UDT), псевдотипов, массивов и множеств.
Встроенный высокопроизводительный драйвер JDBC, использующий внутренние SPI-функции Postgres Pro.
Поддержка метаданных для драйвера JDBC. Включены как
DatabaseMetaData, так иResultSetMetaData.Интеграция с точками сохранения и обработкой исключений Postgres Pro.
Возможность использовать параметры
IN,INOUTиOUT.Два обработчика языка:
javau(поведение функций не ограничено, только суперпользователи могут создавать их) иjava(функции выполняются под управлением менеджера безопасности, который блокирует доступ к файловой системе, а пользователи, имеющие права создавать функции, настраиваются с помощью командGRANTиREVOKE).Слушатели (listeners) транзакций и точек сохранения, позволяющие выполнять код при фиксации или откате транзакции или точки сохранения.
Серверные функции и триггеры пишутся на языке Java с помощью подключённой напрямую эффективной версии стандартного JDBC API, который прозрачно поддерживается pljava, а также с помощью расширенных возможностей, которые есть в pljava API.
Функция или триггер в SQL-коде сопоставляется со статическим методом в Java-классе. Для выполнения функции соответствующий класс должен быть установлен в базе данных. pljava предоставляет набор функций, которые помогают устанавливать и поддерживать Java-классы.
Компилятор Java также создаёт дескриптор развёртывания SQLJ, содержащий SQL-операторы, которые должны быть выполнены при установке и удалении скомпилированного Java-кода обслуживающим процессом Postgres Pro.
Скомпилированный Java-код и файл дескриптора развёртывания хранятся вместе в архиве Java (JAR-файл). Функция sqlj.install_jar загружает код в обслуживающий процесс Postgres Pro и выполняет необходимые SQL-команды из дескриптора развёртывания, делая новые типы, функции и триггеры доступными для использования.
pljava реализует стандартизированный способ передачи параметров и возвращаемых значений. Сложные типы и множества передаются с помощью стандартного класса ResultSet драйвера JDBC. Большое внимание было уделено тому, чтобы не вводить никакие собственные интерфейсы, за исключением случаев крайней необходимости, чтобы Java-код, написанный с помощью pljava, был максимально независимым от баз данных.
Драйвер JDBC включён в pljava. Этот драйвер написан непосредственно поверх внутренних SPI-функций Postgres Pro. Этот драйвер очень важен, поскольку функции и триггеры очень часто повторно используют базу данных. В этих случаях они должны использовать те же границы транзакций, которые использовались вызывающим кодом.
Модуль pljava оптимизирован для повышения производительности. Виртуальная машина Java выполняется в рамках того же обслуживающего процесса. Это гарантирует очень низкие накладные расходы на вызовы. Модуль pljava разработан с целью использовать весь потенциал Java непосредственно в самой базе данных, чтобы бизнес-логика, интенсивно использующая базы данных, могла выполняться максимально близко к фактическим данным.
Стандартный интерфейс Java Native Interface (JNI) используется при передаче вызовов из обслуживающего процесса в виртуальную машину Java и обратно.
H.6.2. Установка #
Модуль pljava поставляется вместе с Postgres Pro Enterprise в виде отдельного пакета pljava-ent-16 (подробная инструкция по установке приведена в Главе 17).
H.6.3. Дескриптор развёртывания SQLJ #
Функции sqlj.install_jar, sqlj.replace_jar и sqlj.remove_jar могут затрагивать дескриптор развёртывания, позволяя SQL-командам выполняться после установки JAR-файла или перед его удалением.
Дескриптор добавляется в JAR-файл как обычный текстовый файл. В манифесте JAR-файла должна быть запись, указывающая на то, что этот файл является дескриптором развёртывания SQLJ.
Name: deployment/examples.ddr SQLJDeploymentDescriptor: TRUE
Такой файл может быть написан вручную в соответствии с форматом ниже, но обычно в исходный код добавляются определённые аннотации Java, как описано в разделе Автоматическая генерация SQL-кода. Затем компилятор генерирует файл дескриптора развёртывания во время компиляции исходного Java-кода. Скомпилированные классы и файл .ddr могут быть вместе помещены в JAR-файл.
Формат дескриптора развёртывания устанавливается стандартом ISO/IEC 9075-13:2003.
<файл_дескриптора> ::= SQLActions <левая_скобка> <правая_скобка> <знак_равенства> { [ <двойные_кавычки> <группа_действий> <двойные_кавычки> [ <запятая> <двойные_кавычки> <группа_действий> <двойные_кавычки> ] ] } <группа_действий> ::= <действия_установки> | <действия_удаления> <действия_установки> ::= BEGIN INSTALL [ <команда> <точка_с_запятой> ]... END INSTALL <действия_удаления> ::= BEGIN REMOVE [ <команда> <точка_с_запятой> ]... END REMOVE <команда> ::= <выражение_SQL> | <блок_исполнителей> <выражение_SQL> ::= <компонент_SQL>... <блок_исполнителей> ::= BEGIN <имя_исполнителя> <компонент_SQL>... END <имя_исполнителя> <имя_исполнителя> ::= <идентификатор> <компонент_SQL> ::= !лексическая единица SQL, указанная под термином"<token>" в подпункте 5.2 в стандарте ISO/IEC 9075-2.
Если используются блоки исполнителей, pljava по умолчанию рассматривает только те, у которых имя исполнителя PostgreSQL (без учёта регистра). Пример дескриптора развёртывания:
SQLActions[] = {
"BEGIN INSTALL
CREATE FUNCTION javatest.java_getTimestamp()
RETURNS timestamp
AS 'org.postgresql.pljava.example.Parameters.getTimestamp'
LANGUAGE java;
END INSTALL",
"BEGIN REMOVE
DROP FUNCTION javatest.java_getTimestamp();
END REMOVE"
}Хотя по умолчанию распознается только имя исполнителя PostgreSQL, можно указать имена исполнителей, подлежащих распознаванию, в виде списка в параметре конфигурации pljava.implementors. Этот список проверяется после каждой команды при выполнении дескриптора развёртывания. Это позволяет коду в дескрипторе приобретать элементарную форму условного управления выполнением, которая достигается путём изменения блока исполнителей для выполнения на основе обнаруженных условий.
H.6.4. Сопоставление функций #
H.6.4.1. Функции #
Java-функция объявляется с именем класса и с публичным статическим методом этого класса. Этот класс разрешается с помощью параметра classpath, заданного для той схемы, в которой объявлена эта функция. Если classpath не был указан для этой схемы, то используется схема public. Учтите, что загрузчик System ClassLoader всегда имеет приоритет. Переопределить классы, загруженные с помощью этого загрузчика, невозможно.
Можно объявить следующую функцию для доступа к статическому методу getProperty класса java.lang.System:
CREATE FUNCTION getsysprop(VARCHAR)
RETURNS VARCHAR
AS 'java.lang.System.getProperty'
LANGUAGE java;
SELECT getsysprop('java.version');И параметры, и возвращаемое значение могут быть указаны явно, поэтому пример выше можно написать следующим образом:
CREATE FUNCTION getsysprop(VARCHAR)
RETURNS VARCHAR
AS 'java.lang.String=java.lang.System.getProperty(java.lang.String)'
LANGUAGE java;Этот способ объявления функции полезен, когда сопоставление по умолчанию является некорректным. pljava использует стандартное явное приведение типов Postgres Pro, когда SQL-тип параметра или возвращаемого значения не соответствует Java-типу, указанному в сопоставлении.
Обратите внимание, что явное приведение типов, которое здесь упоминается, осуществляется не путём создания фактического SQL-выражения CAST, а в основном аналогичными способами.
H.6.4.2. Автоматическая генерация SQL-кода #
Наиболее простой способ написать объявление SQL-функции, которое соответствует Java-коду, — это поручить компилятору Java выполнить следующее:
public class Hello {
@Function
public static String hello(String toWhom) {
return "Hello, " + toWhom + "!";
}
}При компиляции этой функции также создаётся дескриптор развёртывания, содержащий правильное объявление SQL-функций. Когда дескриптор включён в JAR-файл вместе со скомпилированным кодом, функция sqlj.install_jar модуля pljava создаёт объявление SQL-функции во время загрузки файла.
H.6.5. Триггеры #
Сигнатура метода триггера предопределена. Метод триггера всегда должен возвращать тип void и иметь параметр, реализующий интерфейс org.postgresql.pljava.TriggerData. Интерфейс TriggerData предоставляет доступ к двум экземплярам java.sql.ResultSet: один экземпляр представляет старую строку, а другой — новую. Старая строка доступна только для чтения, а новая строка может быть обновлена.
Экземпляры ResultSet доступны только для триггеров, которые срабатывают для каждой строки. Триггеры удаления не имеют новой строки, а триггеры вставки не имеют старой строки. Только у триггеров обновления есть обе строки.
В дополнение к этим экземплярам существуют несколько логических методов, позволяющих получить более подробную информацию о триггере.
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);Соответствующий Java-код выглядит следующим образом:
/**
* Обновить время изменения при обновлении строки
*/
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()));
}H.6.6. Сопоставление типов по умолчанию #
H.6.6.1. Скалярные типы #
Скалярные типы сопоставляются напрямую. Таблица ниже показывает текущие сопоставления.
Таблица H.9. Сопоставление скалярных типов
| Postgres Pro | Java |
|---|---|
bool | boolean |
«char» | byte |
int2 | short |
int4 | int |
int8 | long |
float4 | float |
float8 | double |
char | java.lang.String |
varchar | java.lang.String |
text | java.lang.String |
name | java.lang.String |
bytea | byte[] |
date | java.sql.Date |
time | java.sql.Time (хранимое значение обрабатывается как местное время) |
timetz | java.sql.Time |
timestamp | java.sql.Timestamp (хранимое значение обрабатывается как местное время) |
timestamptz | java.sql.Timestamp |
H.6.6.2. Скалярные типы массивов #
Все скалярные типы могут быть представлены в виде массивов. Хотя Postgres Pro позволяет объявлять многомерные массивы с фиксированными размерами, pljava рассматривает все массивы как имеющие одно измерение (за исключением byte[], который сопоставляется с byte[][]). Причина этого заключается в том, что информация об измерениях и размерах нигде не хранится и никак не применяется.
Однако текущая реализация не устанавливает ограничений на размеры массивов — поведение такое же, как и для массивов с неопределённой длиной.
На самом деле текущая реализация также не контролирует объявленное количество измерений. Массивы, состоящие из элементов определённого типа, считаются массивами этого же типа независимо от размера и количества измерений. Таким образом, указание количества измерений или размера в команде CREATE TABLE не влияет на механизм работы с массивом.
Таблица H.10. Сопоставление скалярных типов массивов
| Postgres Pro | Java |
|---|---|
bool[] | boolean[] |
«char»[] | byte[] |
int2[] | short[] |
int4[] | int[] |
int8[] | long[] |
float4[] | float[] |
float8[] | double[] |
char[] | java.lang.String[] |
varchar[] | java.lang.String[] |
text[] | java.lang.String[] |
name[] | java.lang.String[] |
bytea[] | byte[][] |
date[] | java.sql.Date[] |
time[] | java.sql.Time[] (хранимое значение обрабатывается как местное время) |
timetz[] | java.sql.Time[] |
timestamp[] | java.sql.Timestamp[] (хранимое значение обрабатывается как местное время) |
timestamptz[] | java.sql.Timestamp[] |
H.6.6.3. Типы доменов #
Тип домена сопоставляется в соответствии с типом, который он расширяет, если только не установлено специальное сопоставление для переопределения этого поведения.
H.6.6.4. Псевдотипы #
Таблица H.11. Сопоставление псевдотипов
| Postgres Pro | Java |
|---|---|
«any» | java.lang.Object |
anyelement | java.lang.Object |
anyarray | java.lang.Object[] |
cstring | java.lang.String |
record | java.sql.ResultSet |
trigger | org.postgresql.pljava.TriggerData (обратитесь к разделу Триггеры) |
H.6.6.5. Обработка NULL для примитивов #
Скалярные типы, которые сопоставляются с примитивами Java, не могут передаваться как значения NULL. Чтобы разрешить это, такие типы могут иметь альтернативное сопоставление. Можно сделать это сопоставление, указав его в явном виде в ссылке на метод.
CREATE FUNCTION trueIfEvenOrNull(integer)
RETURNS bool
AS 'foo.fee.Fum.trueIfEvenOrNull(java.lang.Integer)'
LANGUAGE java;В Java-коде надо написать примерно так:
package foo.fee;
public class Fum
{
static boolean trueIfEvenOrNull(Integer value)
{
return (value == null)
? true
: (value.intValue() % 1) == 0;
}
}Следующие операторы должны выдавать true:
SELECT trueIfEvenOrNull(NULL); SELECT trueIfEvenOrNull(4);
Чтобы вернуть значения NULL из Java-метода, используйте тип объектов, который соответствует примитиву (например, возвращайте java.lang.Integer вместо int). Механизм сопоставления pljava найдёт этот метод в любом случае. Поскольку в Java не может быть разных типов возвращаемых значений для методов с одинаковыми именами, это не создаёт никаких неоднозначностей.
Также значения NULL могут быть в массивах. pljava обрабатывает их тем же образом, что и обычные примитивы, например, можно объявить методы, которые используют параметр java.lang.Integer[] вместо параметра int[].
H.6.6.6. Составные типы #
Составной тип по умолчанию передаётся как экземпляр java.sql.ResultSet, доступный только для чтения и содержащий одну строку. ResultSet уже установлен на эту строку, поэтому вызывать next() не нужно. Значения составного типа извлекаются с помощью стандартных методов считывания (геттеров) ResultSet.
CREATE TYPE compositeTest
AS(base integer, incbase integer, ctime timestamptz);
CREATE FUNCTION useCompositeTest(compositeTest)
RETURNS VARCHAR
AS 'foo.fee.Fum.useCompositeTest'
IMMUTABLE LANGUAGE java;В классе Fum добавляется следующий статический метод:
public static String useCompositeTest(ResultSet compositeTest)
throws SQLException
{
int base = compositeTest.getInt(1);
int incbase = compositeTest.getInt(2);
Timestamp ctime = compositeTest.getTimestamp(3);
return "Base = \\"" + base +
"\\", incbase = \\"" + incbase +
"\\", ctime = \\"" + ctime + "\\"";
}H.6.6.7. Сопоставление по умолчанию #
Типы, у которых нет сопоставлений, на текущий момент сопоставляются с типом java.lang.String. При преобразовании значений используются стандартные функции textin и textout Postgres Pro, зарегистрированные для соответствующих типов.
H.6.7. Сопоставление SQL-типа с Java-классом #
С помощью pljava можно установить сопоставление между произвольным типом и Java-классом. Для этого необходимо выполнить следующие предварительные требования:
Необходимо знать структуру хранения SQL-типа, который сопоставляется.
Java-класс, который сопоставляется, должен реализовывать интерфейс
java.sql.SQLData.
H.6.7.1. Сопоставление существующего SQL-типа с Java-классом #
В этом примере показано, как сопоставить геометрический тип точки Postgres Pro с Java-классом. Точка хранится в виде двух значений float8: координаты x и y.
Когда известна структура хранения типа точки, можно создать реализацию java.sql.SQLData, которая использует класс java.sql.SQLInput для чтения данных и класс java.sql.SQLOutput для записи.
package org.postgresql.pljava.example;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class Point implements SQLData {
private double m_x;
private double m_y;
private String m_typeName;
public String getSQLTypeName() {
return m_typeName;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException {
m_x = stream.readDouble();
m_y = stream.readDouble();
m_typeName = typeName;
}
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeDouble(m_x);
stream.writeDouble(m_y);
}
/* Значимый код, который действительно что-то делает с этим типом,
* был намеренно опущен
*/
}Наконец, установите сопоставление типов с помощью команды add_type_mapping:
SELECT sqlj.add_type_mapping('point', 'org.postgresql.pljava.example.Point');Теперь можно использовать этот новый класс. pljava сопоставляет любой параметр с типом точки с классом org.postgresql.pljava.example.Point.
H.6.7.2. Создание составного пользовательского типа и сопоставление его с Java-классом #
Здесь приведён пример сложного типа, созданного как составной пользовательский тип.
CREATE TYPE javatest.complextuple AS (x float8, y float8);
SELECT sqlj.add_type_mapping('javatest.complextuple',
'org.postgresql.pljava.example.ComplexTuple');package org.postgresql.pljava.example;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class ComplexTuple implements SQLData {
private double m_x;
private double m_y;
private String m_typeName;
public String getSQLTypeName()
{
return m_typeName;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
m_typeName = typeName;
m_x = stream.readDouble();
m_y = stream.readDouble();
}
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeDouble(m_x);
stream.writeDouble(m_y);
}
/* Значимый код, который действительно что-то делает с этим типом,
* был намеренно опущен
*/
}H.6.7.3. Автоматическая генерация SQL-кода #
SQL-код, показанный выше для этого примера, будет написан компилятором Java, если для класса ComplexTuple указать аннотацию, что этот класс является «сопоставляемым пользовательским типом», и задать необходимое имя и структуру SQL.
@MappedUDT(schema="javatest", name="complextuple",
structure={"x float8", "y float8"})
public class ComplexTuple implements SQLData {
...Генерация SQL-кода снижает нагрузку на поддержание определений в двух местах.
H.6.8. Создание скалярного пользовательского типа #
В этом тексте предполагается, что уже есть представление о том, как создаются скалярные типы и как они добавляются в систему типов Postgres Pro. За подробной информацией обратитесь к разделу Пользовательские типы.
С точки зрения SQL создание нового скалярного типа с помощью Java-функций очень похоже на создание типа с помощью C-функций, но на самом деле отличается, если посмотреть на фактическую реализацию. Java требует, чтобы сопоставление между Java-классом и соответствующим SQL-типом выполнялось с помощью интерфейсов java.sql.SQLData, java.sql.SQLInput и java.sql.SQLOutput, используемых pljava. Кроме того, система типов Postgres Pro требует, чтобы каждый тип имел текстовое представление.
В примере ниже показано, как создать тип с именем javatest.complex. Имя соответствующего Java-класса будет org.postgresql.pljava.example.ComplexScalar.
Java-класс для скалярного пользовательского типа должен реализовывать интерфейс java.sql.SQLData. Кроме того, он также должен реализовывать метод parse(), который создаёт и возвращает экземпляр этого класса, и метод toString(), который возвращает то, что может быть разобрано методом parse().
package org.postgresql.pljava.example;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
import java.util.logging.Logger;
import org.postgresql.pljava.annotation.Function;
import org.postgresql.pljava.annotation.SQLType;
import org.postgresql.pljava.annotation.BaseUDT;
import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
import static
org.postgresql.pljava.annotation.Function.OnNullInput.RETURNS_NULL;
@BaseUDT(schema="javatest", name="complex",
internalLength=16, alignment=BaseUDT.Alignment.DOUBLE)
public class ComplexScalar implements SQLData
{
private double m_x;
private double m_y;
private String m_typeName;
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
public static ComplexScalar parse(String input, String typeName)
throws SQLException
{
try
{
StreamTokenizer tz = new StreamTokenizer(new StringReader(input));
if(tz.nextToken() == '('
&& tz.nextToken() == StreamTokenizer.TT_NUMBER)
{
double x = tz.nval;
if(tz.nextToken() == ','
&& tz.nextToken() == StreamTokenizer.TT_NUMBER)
{
double y = tz.nval;
if(tz.nextToken() == ')')
{
return new ComplexScalar(x, y, typeName);
}
}
}
throw new SQLException("Unable to parse complex from string \""
+ input + '"');
}
catch(IOException e)
{
throw new SQLException(e.getMessage());
}
}
public ComplexScalar()
{
}
public ComplexScalar(double x, double y, String typeName)
{
m_x = x;
m_y = y;
m_typeName = typeName;
}
@Override
public String getSQLTypeName()
{
return m_typeName;
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
m_x = stream.readDouble();
m_y = stream.readDouble();
m_typeName = typeName;
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeDouble(m_x);
stream.writeDouble(m_y);
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public String toString()
{
s_logger.info(m_typeName + " toString");
StringBuffer sb = new StringBuffer();
sb.append('(');
sb.append(m_x);
sb.append(',');
sb.append(m_y);
sb.append(')');
return sb.toString();
}
/* Значимый код, который действительно что-то делает с этим типом,
* был намеренно опущен
*/
}Для самого класса указана аннотация @BaseUDT, задающая SQL-схему, имя, а также длину и выравнивание, необходимые для внутренней формы хранения.
Так как компилятор знает, что класс является BaseUDT, он ожидает наличие методов parse(), toString(), readSQL() и writeSQL() и будет генерировать корректный SQL-код, чтобы объявить их в виде функций для Postgres Pro. Аннотации @Function здесь используются только, чтобы объявить функцию как постоянную, и определить поведение при входном значении NULL, поскольку эти значения не используются по умолчанию при объявлении функции.
H.6.9. Возврат сложных типов #
pljava обрабатывает возвращаемое значение сложного типа как параметр IN или OUT. Если объявлена функция, которая возвращает сложный тип, нужно использовать Java-метод с логическим возвращаемым значением и с последним параметром типа java.sql.ResultSet, добавленным после всех видимых параметров метода. Выходной параметр будет инициализирован экземпляром ResultSet, который содержит одну строку и может обновляться.
CREATE FUNCTION createComplexTest(int, int) RETURNS complexTest AS 'foo.fee.Fum.createComplexTest' IMMUTABLE LANGUAGE java;
Механизм сопоставления метода pljava теперь будет находить следующий метод в классе foo.fee.Fum:
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;
}Возвращаемое значение указывает, следует ли считать параметр receiver актуальным кортежем (true) или NULL (false).
H.6.10. Функции, возвращающие множества #
Возвращать множества довольно сложно. Не нужно сначала строить всё множество, а затем возвращать его, поскольку большие множества требуют излишних ресурсов. Лучше создавать по одной строке за один раз. Именно этого и ожидает обслуживающий процесс Postgres Pro от функции, которая возвращает SETOF <type>. <type> может быть скалярным типом, например int, float или varchar, сложным типом или типом RECORD.
H.6.10.1. Возврат множества скалярного типа #
Чтобы вернуть множество скалярного типа, нужно создать Java-метод, который возвращает реализацию интерфейса java.util.Iterator.
CREATE FUNCTION javatest.getNames() RETURNS SETOF varchar AS 'foo.fee.Bar.getNames' IMMUTABLE LANGUAGE java;
Соответствующий Java-класс:
package foo.fee;
import java.util.Iterator;
import org.postgresql.pljava.annotation.Function;
import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
public class Bar
{
@Function(schema="javatest", effects=IMMUTABLE)
public static Iterator<String> getNames()
{
ArrayList<String> names = new ArrayList<>();
names.add("Lisa");
names.add("Bob");
names.add("Bill");
names.add("Sally");
return names.iterator();
}
}H.6.10.2. Возврат множества сложного типа #
Метод, возвращающий множество сложного типа, должен использовать либо интерфейс org.postgresql.pljava.ResultSetProvider, либо org.postgresql.pljava.ResultSetHandle. Причина наличия двух интерфейсов заключается в необходимости оптимальной обработки для двух различных сценариев использования. Первый интерфейс отлично подходит, когда нужно динамически создавать каждую строку, которая должна быть возвращена функцией SETOF. Второй интерфейс имеет смысл, когда нужно вернуть результат выполненного запроса.
H.6.10.2.1. Использование интерфейса ResultSetProvider #
Этот интерфейс имеет два метода: boolean assignRowValues(java.sql.ResultSet tupleBuilder, int rowNumber) и void close(). Анализатор запросов Postgres Pro будет последовательно вызывать метод assignRowValues, пока он не вернёт false или пока анализатор не решит, что ему больше не нужно строк. Затем он вызовет метод close.
Этот интерфейс можно использовать следующим образом:
CREATE FUNCTION javatest.listComplexTests(int, int) RETURNS SETOF complexTest AS 'foo.fee.Fum.listComplexTest' IMMUTABLE LANGUAGE java;
Эта функция сопоставляется со статическим Java-методом, который возвращает экземпляр, реализующий интерфейс 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
{
// Остановить при достижении 12 строк
//
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 void close()
{
// В этом примере ничего не требуется
}
@Function(effects=IMMUTABLE, schema="javatest", type="complexTest")
public static ResultSetProvider listComplexTests(int base, int increment)
throws SQLException
{
return new Fum(base, increment);
}
}Метод listComplexTests(int base, int increment) вызывается один раз. Он может вернуть NULL, если нет доступных результатов, или экземпляр ResultSetProvider. Здесь класс Fum реализует этот интерфейс, поэтому он может вернуть экземпляр самого себя. Затем несколько раз будет вызываться метод assignRowValues(ResultSet receiver, int currentRow), пока он не вернёт false. В это время будет вызван метод close.
В некоторых случаях параметр currentRow может быть полезен, а в других — не нужен. При первом вызове для параметра будет передаваться значение 0, при каждом последующем вызове значение будет увеличиваться на 1. Если экземпляр ResultSetProvider возвращает результаты из какого-либо источника (например, Iterator), который запоминает свою позицию, то параметр currentRow может просто игнорироваться.
H.6.10.2.2. Использование интерфейса ResultSetHandle #
Этот интерфейс похож на интерфейс ResultSetProvider тем, что у него тоже есть метод close, который вызывается в конце. Но вместо метода, вызываемого анализатором для формирования одной строки за раз, у этого интерфейса есть метод, который возвращает ResultSet. Анализатор запросов будет перебирать это множество и передавать его содержимое по одному кортежу за раз, пока функция next не вернёт false или пока анализатор не решит, что больше строк не требуется.
Здесь приведён пример, в котором выполняется запрос с использованием оператора, полученного с помощью подключения по умолчанию. SQL-код выглядит так:
CREATE FUNCTION javatest.listSupers() RETURNS SETOF pg_user AS 'org.postgresql.pljava.example.Users.listSupers' LANGUAGE java; CREATE FUNCTION javatest.listNonSupers() RETURNS SETOF pg_user AS 'org.postgresql.pljava.example.Users.listNonSupers' LANGUAGE java;
Java-код выглядит так:
public class Users implements ResultSetHandle
{
private final String m_filter;
private Statement m_statement;
public Users(String filter)
{
m_filter = filter;
}
public ResultSet getResultSet()
throws SQLException
{
m_statement = DriverManager.getConnection("jdbc:default:connection")
.createStatement();
return m_statement.executeQuery("SELECT * FROM pg_user WHERE " + m_filter);
}
public void close()
throws SQLException
{
m_statement.close();
}
@Function(schema="javatest", type="pg_user")
public static ResultSetHandle listSupers()
{
return new Users("usesuper = true");
}
@Function(schema="javatest", type="pg_user")
public static ResultSetHandle listNonSupers()
{
return new Users("usesuper = false");
}
}H.6.11. Использование JDBC #
pljava содержит драйвер JDBC, который сопоставляется с SPI-функциями Postgres Pro. Подключение, которое сопоставляется с текущей транзакцией, можно получить с помощью следующего оператора:
Connection conn = DriverManager.getConnection("jdbc:default:connection");Теперь можно подготавливать и выполнять операторы, как и с любым другим подключением JDBC. Есть несколько ограничений:
Транзакцией нельзя управлять никаким способом. Таким образом, нельзя использовать методы подключения, например:
commit()rollback()setAutoCommit()setTransactionIsolation()
Точка сохранения не может существовать дольше, чем функция, в которой она была установлена, и её также необходимо откатить или освободить с помощью той же функции.
Для экземпляров
ResultSet, которые возвращаются функциейexecuteQuery(), всегда используются константыFETCH_FORWARD(считывание в прямом порядке) иCONCUR_READ_ONLY(доступ только для чтения).Интерфейс
CallableStatement(для хранимых процедур) ещё не реализован.Типы
Clob/Blobнуждаются в доработке. Типыbyte[]иStringхорошо работают для типовbyteaиtext, соответственно. Планируется более эффективное сопоставление, при котором сам массив не копируется.
H.6.12. Обработка исключений #
В обслуживающем процессе Postgres Pro можно перехватывать и обрабатывать исключения точно так же, как и любое другое исключение. Серверная структура ErrorData представлена в виде свойства класса ServerException, унаследованного от java.sql.SQLException. Механизм try/catch в Java синхронизирован с механизмом в обслуживающем процессе.
Примечание
По нескольким причинам в настоящее время не рекомендуется ссылаться на ServerException и ErrorData из кода, а в будущем это может стать невозможным. В будущих версиях ожидается улучшение этого механизма. До тех пор рекомендуется по возможности использовать только стандартный класс java.sql.SQLException, предоставляемый Java API, и его стандартные атрибуты (такие как SQLState).
pljava всегда будет перехватывать исключения, которые вы не перехватываете. Они вызовут ошибку Postgres Pro, и сообщение будет записано в журнал с помощью утилит протоколирования Postgres Pro. Также будет выводиться трассировка стека исключения, если для параметра конфигурации log_min_messages установлено значение DEBUG1 или ниже.
Примечание
Когда обслуживающий процесс выдаёт исключение, нельзя продолжить выполнение серверных функций, пока функция не вернёт результат и ошибка не будет распространена, если только вы не использовали точку сохранения. При откате точки сохранения условие исключения сбрасывается, и выполнение может продолжиться.
H.6.13. Точки сохранения #
Для точек сохранения Postgres Pro можно использовать стандартные методы setSavepoint() и releaseSavepoint() интерфейса java.sql.Connection. Применяются следующие ограничения:
Точку сохранения необходимо откатить или освободить в той же функции, в которой она была установлена.
Точка сохранения не должна существовать дольше, чем функция, в которой она была установлена.
Здесь под «функцией» имеется в виду функция pljava, которая вызывается из SQL-кода. Ограничения не запрещают организовывать Java-код в несколько методов, но точка сохранения не может существовать после окончательного возврата из Java-кода в вызывающий SQL-код.
H.6.14. Протоколирование #
pljava использует стандартный класс java.util.logging.Logger. Поэтому можно написать код так:
Logger.getAnonymousLogger().info(
"Time is " + new Date(System.currentTimeMillis()));В настоящее время Logger жёстко привязан к обработчику, который сопоставляет уровень протоколирования, указанный в параметре конфигурации log_min_messages, с корректным уровнем Logger и выводит все сообщения с помощью серверной функции ereport().
Важно отметить, что методы Logger позволяют быстро отбросить любое сообщение, которое протоколируется на более детальном уровне, чем уровень, сопоставленный из параметра Postgres Pro во время первого использования pljava в текущем сеансе. Такие сообщения даже никогда не доходят до функции ereport(), даже если значение параметра Postgres Pro позже изменяется.
Таким образом, если ожидаемые сообщения из Java-кода не показываются, убедитесь, что параметры Postgres Pro настроены достаточным образом во время первого использования pljava в сеансе, чтобы Java-код не отбрасывал эти сообщения. После запуска pljava параметры могут изменяться по мере необходимости и будут обычным образом управлять тем, что функция ereport() делает с сообщениями, которые pljava доставляет в неё.
Уровень для отсечки в Java устанавливается на основе более точного значения из значений параметров log_min_messages и client_min_messages.
Следующее сопоставление применяется между уровнями Logger и уровнями Postgres Pro:
Таблица H.12. Сопоставление уровней протоколирования
| Уровень java.util.logging.Level | Уровень Postgres Pro |
|---|---|
| SEVERE | ERROR |
| WARNING | WARNING |
| INFO | INFO |
| FINE | DEBUG1 |
| FINER | DEBUG2 |
| FINEST | DEBUG3 |
H.6.15. Функции SQLJ #
-
sqlj.install_jar# Загружает JAR-файл из местоположения, указанного в URL, в репозиторий SQLJ. Если JAR-файл с таким именем уже существует в репозитории, возникает ошибка.
Использование:
SELECT sqlj.install_jar(<
url_jar>, <имя_jar>, <развернуть>);Параметры:
url_jar: URL, указывающий местоположение JAR-файла, который должен быть загружен.имя_jar: имя, по которому можно обращаться к JAR-файлу после его загрузки.развернуть:true, если JAR-файл должен быть развёрнут в соответствии с дескриптором развёртывания, в противном случаеfalse.
-
sqlj.replace_jar# Заменяет загруженный JAR-файл другим JAR-файлом. Используйте его, чтобы обновить уже загруженные файлы. Если JAR-файл не найден, возникает ошибка.
Использование:
SELECT sqlj.replace_jar(<
url_jar>, <имя_jar>, <повторно_развернуть>);Параметры:
url_jar: URL, указывающий местоположение JAR-файла, который должен быть загружен.имя_jar: имя JAR-файла, который должен быть заменён.повторно_развернуть:true, если JAR-файл должен быть удалён в соответствии с дескриптором развёртывания старого JAR-файла и заново развёрнут в соответствии с дескриптором развёртывания нового JAR-файла, в противном случаеfalse.
-
sqlj.remove_jar# Удаляет JAR-файл из репозитория JAR. Любое значение
classpath, которое ссылается на этот JAR-файл, обновляется соответствующим образом. Если JAR-файл не найден, возникает ошибка.Использование:
SELECT sqlj.remove_jar(<
имя_jar>, <отменить_развёртывание>);Параметры:
имя_jar: имя JAR-файла, который должен быть удалён.отменить_развёртывание:true, если для JAR-файла нужно отменить развёртывание в соответствии с дескриптором развёртывания, в противном случаеfalse.
-
sqlj.get_classpath# Возвращает значение
classpath, которое было задано для указанной схемы. Если для схемы не задано значениеclasspath, возвращаетсяNULL. Если указанная схема не существует, возникает ошибка.Использование:
SELECT sqlj.get_classpath(<
схема>);Параметры:
схема: имя схемы.
-
sqlj.set_classpath# Определяет значение
classpathдля указанной схемы.classpathпредставляет собой список имён JAR-файлов, разделённых двоеточиями. Если указанная схема не существует или один или несколько имён JAR-файлов обращаются к несуществующим файлам, возникает ошибка.Использование:
SELECT sqlj.set_classpath(<
схема>, <classpath>);Параметры:
схема: имя схемы.classpath: список имён JAR-файлов, разделённых двоеточиями.
-
sqlj.add_type_mapping# Устанавливает сопоставление между SQL-типом и Java-классом. После создания сопоставления параметры и возвращаемые значения сопоставляются соответствующим образом. За подробной информацией обратитесь к разделу Сопоставление SQL-типа с Java-классом.
Использование:
SELECT sqlj.add_type_mapping(<
тип_sql>, <класс_java>);Параметры:
тип_sql: имя SQL-типа. Имя может быть дополнено схемой (пространством имён). Если схема опущена, она будет определена в соответствии с текущим значением параметраsearch_path.класс_java: имя класса. Класс должен быть найден по значениюclasspath, которое актуально для текущей схемы.
-
sqlj.drop_type_mapping# Удаляет сопоставление между SQL-типом и Java-классом.
Использование:
SELECT sqlj.drop_type_mapping(<
тип_sql>);Параметры:
тип_sql: имя SQL-типа. Имя может быть дополнено схемой (пространством имён). Если схема опущена, она будет определена в соответствии с текущим значением параметраsearch_path.
Примечание
Функции install_jar и replace_jar принимают URL (к которому у сервера должен быть доступ) к JAR-файлу. Используя правила для URL JAR-файлов, можно также создать URL, который обращается к JAR-файлу внутри другого JAR-файла. Например:
jar:file:outer.jar!/inner.jar
Однако кеширование «внешнего» JAR-файла может помешать попыткам заменить или перезагрузить более новую версию в рамках одного и того же сеанса.
H.6.16. Параметры конфигурации #
Несколько параметров конфигурации могут влиять на работу pljava, включая некоторые общие параметры Postgres Pro, а также собственные параметры pljava.
H.6.16.1. Параметры Postgres Pro #
-
check_function_bodies# Влияет на то, насколько строго pljava проверяет новую функцию во время выполнения команды
CREATE FUNCTIONили при установке JAR-файла, если среди его действий развёртывания выполняетсяCREATE FUNCTION. Если для параметраcheck_function_bodiesустановлено значениеon, pljava проверяет, что задействованные класс и метод могут быть загружены и сопоставлены. Если задействованный класс зависит от классов в других JAR-файлах, то эти файлы тоже должны быть установлены и указаны вclasspath, поэтому загрузка JAR-файлов с зависимостями в неправильном порядке может повлечь ошибки проверки. Если для параметраcheck_function_bodiesустановлено значениеoff, во время выполненияCREATE FUNCTIONпроверяется только базовый синтаксис, поэтому можно объявлять функции и устанавливать JAR-файлы в любом порядке, но при этом откладывая любые ошибки, связанные с неразрешёнными зависимостями, до более позднего момента, когда эти функции будут использованы.-
dynamic_library_path# Влияет на то, где можно найти встроенные объекты кода pljava, если для команды
LOADне указан полный путь.-
server_encoding# Влияет на все текстовые и символьные строки, которыми обмениваются Postgres Pro и Java. Строго рекомендуется указывать
UTF8для кодировки баз данных и сервера. Если используется другая кодировка, то это должна быть любая из доступных полностью определённых кодировок символов. В частности, псевдокодировкаSQL_ASCIIPostgres Pro не полностью определяет, что представляют любые значения за рамками ASCII. Она применима, но имеет ограничения.
H.6.16.2. Параметры pljava #
-
pljava.allow_unenforced# Используется только при запуске pljava без применения политик безопасности и представляет собой список названий языков (например,
javauиjava), на которых разрешается выполнение функций. Этот параметр имеет пустое значение по умолчанию. Изменять его нужно с осторожностью.-
pljava.allow_unenforced_udt# Используется только при запуске pljava без применения политик безопасности и определяет, разрешено ли выполнять функции преобразования данных, связанные с сопоставляемыми пользовательскими типами pljava. Значение по умолчанию —
off. Изменять его нужно с осторожностью.-
pljava.enable# Установка для этого параметра значения
offпредотвращает завершение запуска pljava до тех пор, пока для параметра позже не будет установлено значениеon. Это может быть полезно в целях отладки.-
pljava.implementors# Список «имён исполнителей», которые распознаёт pljava при обработке дескрипторов развёртывания внутри устанавливаемого или удаляемого JAR-файла. Дескрипторы развёртывания могут содержать команды без имени исполнителя, выполняемые всегда, или команды с именем исполнителя, выполняемые только в системах, распознающих это имя. По умолчанию этот список содержит только значение
postgresql. Дескриптор развёртывания, содержащий команды с другими именами исполнителей, может обеспечивать элементарную форму условного выполнения, если более ранние команды изменяют этот список имён. Элементы этого списка разделены запятыми. Элементы, не являющиеся обычными идентификаторами, должны быть заключены в двойные кавычки.-
pljava.java_thread_pg_entry# Выбор из значений
allow,error,blockилиthrow, контролирующих управление потоками pljava. В Java активно используется многопоточность, в то время как доступ к Postgres Pro одновременно несколькими потоками бывает невозможен. По историческим причинам pljava использует значениеallow, которое сериализует доступ потоков Java к Postgres Pro, позволяя другому потоку Java получать доступ только тогда, когда текущий поток вызывается или возвращается в Java-код . В pljava ранее использовались финализаторы Java-объектов, что требовало такого подхода, поскольку финализаторы выполняются в собственном потоке.Сам модуль pljava больше не требует возможности для потоков получать доступ к Postgres Pro, кроме исходного основного потока. Однако пользовательский код, разработанный для pljava может по-прежнему полагаться на такую возможность. Чтобы проверить это, можно использовать значение
errorилиthrow, и при любой попытке потока, отличного от основного, получить доступ к Postgres Pro возникнет исключение (и трассировка стека, записанная в стандартный канал ошибок сервера). При уверенности, что отсутствует код, которому требуется входить в Postgres Pro, за исключением основного потока, можно использовать значениеblock. Это позволит избежать частых получений и освобождений блокировок pljava при переходе основного потока между Postgres Pro и Java и просто навсегда заблокирует любой другой поток Java, который пытается войти в Postgres Pro. Это значение является эффективным, но может приводить к заблокированным потокам или взаимоблокировкам в обслуживающем процессе, если используется с кодом, который пытается получить доступ к Postgres Pro из более чем одного потока.Значение
throwочень похоже на значениеerror, но более эффективно. При значенииerrorпопытка входа неправильным потоком обнаруживается в C-коде только после операции блокировки и вызывается через JNI. При значенииthrowоперации блокировки опускаются, и попытка входа неправильным потоком не приводит к вызову JNI, а исключение выводится напрямую в Java.-
pljava.libjvm_location# Используется pljava для загрузки среды выполнения Java. Полный путь к разделяемому объекту
libjvm. Версия библиотеки Java, на которую указывает этот параметр, определяет, может ли pljava запускаться с применением политик безопасности или без них.-
pljava.module_path# Путь к модулю, который будет передан системному загрузчику классов Java. Значение по умолчанию вычисляется из конфигурации Postgres Pro и обычно является корректным, если только файлы pljava не установлены в нестандартном месте. Если путь необходимо установить явно, то должно быть как минимум две записи (и обычно только две): JAR-файл с API pljava и JAR-файл с внутренним устройством pljava.
-
pljava.policy_urls# Используется только при запуске pljava с применением политик безопасности. При запуске без применения политик этот параметр игнорируется. Он представляет собой список URL к файлам политик безопасности Java, которые определяют права, доступные функциям pljava. Каждый URL должен быть заключён в двойные кавычки. Если двойные кавычки являются частью URL, то для них можно указать двойные кавычки два раза (в стиле SQL) или
%22, как в соглашении URL. В качестве разделителя между URL в двойных кавычках используется запятая.Файл
java.securityинсталляции Java обычно определяет следующие расположения файлов политик:Общесистемная политика от поставщика Java, достаточная для штатного функционирования самой среды выполнения Java.
Пользовательское местоположение, в котором при наличии файла политик он может дополнять политику из общесистемного файла.
Список из параметра
pljava.policy_urlsизменяет список из инсталляции Java. По умолчанию это происходит после первой записи, при этом сохраняется общесистемная политика, поставляемая Java, но заменяется привычный пользовательский файл. Возможно, этого файла нет в домашнем каталоге пользователяpostgres, а если есть, то он не предназначен для pljava.Любая запись в этом списке может начинаться с
n =(внутри кавычек) для положительного целого числаn, чтобы указать, какую запись в списке местоположений политик Java нужно заменить. Запись1соответствует общесистемной политике,2— привычному пользовательскому файлу. URL, не имеющие префиксаn =, рассматриваются последовательно. Если у первой записи тоже нет префикса, подразумевается2=.Последняя запись
=(в требуемых двойных кавычках) предотвращает использование оставшихся записей в настроенном списке Java.Значение по умолчанию —
"file:${org.postgresql.sysconfdir}/pljava.policy","=".-
pljava.release_lingering_savepoints# Определяет, как возвращаемое значение из функции pljava обрабатывает точки сохранения, которые были созданы в функции, но не были освобождены (аналог «фиксации» для точек сохранения) или откачены. При значении
off(по умолчанию) они откатываются. При значенииonони освобождаются/фиксируются. По возможности вместо установки для этого параметра значенияonбезопаснее было бы исправить функцию таким образом, чтобы освобождать её точки сохранения, когда это необходимо.-
pljava.statement_cache_size# Количество последних подготовленных операторов, которые pljava может держать открытыми.
-
pljava.vmoptions# Любые параметры для передачи среде выполнения Java в том же формате, что и параметры, описанные в документации для команды
java. Строка разбивается по пробелам, если только пробелы не заключены в одинарные или двойные кавычки. При использовании обратной косой черты следующий за ней символ считается буквально, но сама обратная косая черта остаётся в строке, поэтому не все значения могут быть выражены с помощью этих правил. Если кодировка сервера отличается отUTF8, только символы ASCII должны быть использованы в параметреpljava.vmoptions.
H.6. pljava — adding Java stored procedures, triggers, and functions to Postgres Pro backend #
- H.6.1. Description
- H.6.2. Installation
- H.6.3. SQLJ Deployment Descriptor
- H.6.4. Function Mapping
- H.6.5. Triggers
- H.6.6. Default Type Mapping
- H.6.7. Mapping SQL Type to Java Class
- H.6.8. Creating Scalar User-Defined Type
- H.6.9. Returning Complex Types
- H.6.10. Set-Returning Functions
- H.6.11. Using JDBC
- H.6.12. Exception Handling
- H.6.13. Savepoints
- H.6.14. Logging
- H.6.15. SQLJ Functions
- H.6.16. Configuration Parameters
- H.6.2. Installation
H.6.1. Description #
The pljava module allows stored procedures, triggers, and functions to be written in the Java language and executed in the Postgres Pro backend.
pljava provides the following main features:
An ability to write functions, triggers, and user-defined types using recent Java versions.
Standardized utilities to install and maintain Java code in a database.
Standardized mappings of parameters and results. Supports scalar and composite user-defined types (UDTs), pseudo-types, arrays, and sets.
An embedded high-performance JDBC driver utilizing the internal Postgres Pro SPI routines.
Metadata support for the JDBC driver. Both
DatabaseMetaDataandResultSetMetaDataare included.Integration with Postgres Pro savepoints and exception handling.
An ability to use
IN,INOUT, andOUTparameters.Two language handlers:
javau(functions are not restricted in behavior, only superusers can create them) andjava(functions run under a security manager blocking filesystem access, users who can create them are configured withGRANT/REVOKE).Transaction and savepoint listeners enabling code execution when a transaction or savepoint is committed or rolled back.
Backend functions and triggers are written in Java using a directly-connected efficient version of the standard Java JDBC API that pljava transparently provides, with enhanced capabilities found in the pljava API.
A function or trigger in SQL resolves to a static method in a Java class. In order for the function to execute, the appointed class must be installed in the database. pljava adds a set of functions that help installing and maintaining Java classes.
The Java compiler also writes an SQLJ deployment descriptor containing the SQL statements that must be executed when installing and uninstalling the compiled Java code in the Postgres Pro backend.
The compiled Java code and the deployment descriptor file are stored together in a Java archive (JAR file). The sqlj.install_jar function both loads the code into Postgres Pro backend and executes the necessary SQL commands in the deployment descriptor, making new types, functions, and triggers available for use.
pljava implements a standardized way of passing parameters and return values. Complex types and sets are passed using the standard JDBC ResultSet class. Great care was taken not to introduce any proprietary interfaces unless absolutely necessary so that Java code written using pljava becomes as database agnostic as possible.
A JDBC driver is included in pljava. This driver is written directly on top of the internal Postgres Pro SPI routines. This driver is essential since it is very common for functions and triggers to reuse the database. When they do, they must use the same transactional boundaries that where used by the caller.
pljava is optimized for performance. The Java virtual machine executes within the same process as the backend itself. This vouches for a very low call overhead. pljava is designed with the objective to enable the power of Java to the database itself so that database intensive business logic can execute as close to the actual data as possible.
The standard Java Native Interface (JNI) is used when bridging calls from the backend into the Java virtual machine and vice versa.
H.6.2. Installation #
pljava is provided with Postgres Pro Enterprise as a separate pre-built package pljava-ent-16 (for the detailed installation instructions, see Chapter 17).
H.6.3. SQLJ Deployment Descriptor #
The sqlj.install_jar, sqlj.replace_jar, and sqlj.remove_jar functions can act on a deployment descriptor allowing SQL commands to be executed after the JAR file was installed or prior to removal.
The descriptor is added as a normal text file to your JAR file. In the manifest of the JAR file, there must be an entry that appoints the file as the SQLJ deployment descriptor.
Name: deployment/examples.ddr SQLJDeploymentDescriptor: TRUE
Such a file can be written by hand according to the format below but the usual method is to add specific Java annotations in the source code, as described in the Generating SQL Automatically section. The Java compiler then generates the deployment descriptor file at the same time it compiles the Java sources, and the compiled classes and .ddr file can all be placed in the JAR file together.
The format of the deployment descriptor is stipulated by ISO/IEC 9075-13:2003.
<descriptor_file> ::= SQLActions <left_bracket> <right_bracket> <equal_sign> { [ <double_quote> <action_group> <double_quote> [ <comma> <double_quote> <action_group> <double_quote> ] ] } <action_group> ::= <install_actions> | <remove_actions> <install_actions> ::= BEGIN INSTALL [ <command> <semicolon> ]... END INSTALL <remove_actions> ::= BEGIN REMOVE [ <command> <semicolon> ]... END REMOVE <command> ::= <SQL_statement> | <implementor_block> <SQL_statement> ::= <SQL_token>... <implementor_block> ::= BEGIN <implementor_name> <SQL_token>... END <implementor_name> <implementor_name> ::= <identifier> <SQL_token> ::= !an SQL lexical unit specified by the term "<token>"in Sub clause 5.2, "<token> and <separator>", in ISO/IEC 9075-2.
If implementor blocks are used, pljava considers only those with the PostgreSQL implementor name (case insensitive) by default. Here is a sample deployment descriptor:
SQLActions[] = {
"BEGIN INSTALL
CREATE FUNCTION javatest.java_getTimestamp()
RETURNS timestamp
AS 'org.postgresql.pljava.example.Parameters.getTimestamp'
LANGUAGE java;
END INSTALL",
"BEGIN REMOVE
DROP FUNCTION javatest.java_getTimestamp();
END REMOVE"
}
Although, by default, only the PostgreSQL implementor name is recognized, the implementor name(s) to be recognized can be set as a list in the pljava.implementors configuration parameter. It is consulted after every command while executing a deployment descriptor, which gives code in the descriptor a rudimentary form of conditional execution control, by changing which implementor blocks will be executed based on discovered conditions.
H.6.4. Function Mapping #
H.6.4.1. Functions #
A Java function is declared with the name of a class and a public static method on that class. The class is resolved using classpath that was defined for the schema where the function is declared. If no classpath was defined for that schema, the public schema is used. Note that the System ClassLoader always takes precedence. There is no way to override classes loaded with that loader.
The following function can be declared to access the static getProperty method of the java.lang.System class:
CREATE FUNCTION getsysprop(VARCHAR)
RETURNS VARCHAR
AS 'java.lang.System.getProperty'
LANGUAGE java;
SELECT getsysprop('java.version');
Both the parameters and the return value can be explicitly stated, so the example above can also be written as follows:
CREATE FUNCTION getsysprop(VARCHAR)
RETURNS VARCHAR
AS 'java.lang.String=java.lang.System.getProperty(java.lang.String)'
LANGUAGE java;
This way of declaring the function is useful when the default mapping is inadequate. pljava uses a standard Postgres Pro explicit cast when the SQL type of the parameter or return value does not correspond to the Java type defined in the mapping.
Note that the explicit cast here referred to is not accomplished by creating an actual SQL CAST expression but by mostly equivalent means.
H.6.4.2. Generating SQL Automatically #
The simplest way to write the SQL function declaration that corresponds to your Java code is to have the Java compiler to do the following:
public class Hello {
@Function
public static String hello(String toWhom) {
return "Hello, " + toWhom + "!";
}
}
When this function is compiled, a deployment descriptor containing the right SQL function declaration is also produced. When it is included in a JAR file with the compiled code, the sqlj.install_jar function of pljava creates the SQL function declaration at the same time it loads the file.
H.6.5. Triggers #
The method signature of a trigger is predefined. A trigger method must always return void and have the org.postgresql.pljava.TriggerData parameter. The TriggerData interface provides access to two java.sql.ResultSet instances: one representing the old row and one representing the new row. The old row is read-only, while the new row can be updated.
ResultSets 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 exist to gain more information about the 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 corresponding Java code looks as follows:
/**
* 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()));
}
H.6.6. Default Type Mapping #
H.6.6.1. Scalar Types #
Scalar types are mapped in a straightforward way. The table below shows the current mappings.
Table H.9. Scalar Type Mapping
| Postgres Pro | Java |
|---|---|
bool | boolean |
“char” | byte |
int2 | short |
int4 | int |
int8 | long |
float4 | float |
float8 | double |
char | java.lang.String |
varchar | java.lang.String |
text | java.lang.String |
name | 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 |
H.6.6.2. Scalar Array Types #
All scalar types can be represented as an array. Although Postgres Pro allows you to declare multidimensional arrays with fixed sizes, pljava treats all arrays as having one dimension (with the exception of byte[], which maps to byte[][]). The reason for this is that the information about dimensions and sizes is not stored anywhere and not enforced in any way.
However, the current implementation does not enforce the array size limits — the behavior is the same as for arrays of unspecified length.
Actually, the current implementation does not enforce the declared number of dimensions either. Arrays of a particular element type are considered to be of the same type regardless of the size or number of dimensions. So, declaring the number of dimensions or sizes in CREATE TABLE does not affect run-time behavior.
Table H.10. Scalar Array Type Mapping
| Postgres Pro | Java |
|---|---|
bool[] | boolean[] |
“char”[] | byte[] |
int2[] | short[] |
int4[] | int[] |
int8[] | long[] |
float4[] | float[] |
float8[] | double[] |
char[] | java.lang.String[] |
varchar[] | java.lang.String[] |
text[] | java.lang.String[] |
name[] | 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[] |
H.6.6.3. Domain Types #
A domain type is mapped in accordance with the type that it extends unless you installed a specific mapping to override that behavior.
H.6.6.4. Pseudo-Types #
Table H.11. Pseudo-Type Mapping
| Postgres Pro | Java |
|---|---|
“any” | java.lang.Object |
anyelement | java.lang.Object |
anyarray | java.lang.Object[] |
cstring | java.lang.String |
record | java.sql.ResultSet |
trigger | org.postgresql.pljava.TriggerData (see Triggers) |
H.6.6.5. NULL Handling of Primitives #
Scalar types that map to Java primitives cannot be passed as NULL values. To enable this, those types can have an alternative mapping. You can enable this mapping by denoting it in the method reference explicitly.
CREATE FUNCTION trueIfEvenOrNull(integer)
RETURNS bool
AS 'foo.fee.Fum.trueIfEvenOrNull(java.lang.Integer)'
LANGUAGE java;
In Java code, you should have something like:
package foo.fee;
public class Fum
{
static boolean trueIfEvenOrNull(Integer value)
{
return (value == null)
? true
: (value.intValue() % 1) == 0;
}
}
The following statements should yield true:
SELECT trueIfEvenOrNull(NULL); SELECT trueIfEvenOrNull(4);
To return NULL values from a Java method, use the object type that corresponds to the primitive (i.e. return java.lang.Integer instead of int). pljava resolver mechanism finds the method anyway. Since Java cannot have different return types for methods with the same name, this does not introduce any ambiguities.
It is also possible to have NULL values in arrays. pljava handles them in the same way as with normal primitives, i.e. you can declare methods that use a java.lang.Integer[] parameter instead of a int[] parameter.
H.6.6.6. Composite Types #
A composite type is passed as a read-only java.sql.ResultSet instance with one row by default. ResultSet is positioned on its row, so no call to next() should be made. Values of the composite type are retrieved using the standard getter methods of ResultSet.
CREATE TYPE compositeTest
AS(base integer, incbase integer, ctime timestamptz);
CREATE FUNCTION useCompositeTest(compositeTest)
RETURNS VARCHAR
AS 'foo.fee.Fum.useCompositeTest'
IMMUTABLE LANGUAGE java;
In the Fum class, the following static method is added:
public static String useCompositeTest(ResultSet compositeTest)
throws SQLException
{
int base = compositeTest.getInt(1);
int incbase = compositeTest.getInt(2);
Timestamp ctime = compositeTest.getTimestamp(3);
return "Base = \\"" + base +
"\\", incbase = \\"" + incbase +
"\\", ctime = \\"" + ctime + "\\"";
}
H.6.6.7. Default Mapping #
Types that have no mapping are currently mapped to java.lang.String. The standard Postgres Pro textin/textout routines registered for respective types are used when values are converted.
H.6.7. Mapping SQL Type to Java Class #
Using pljava, you can install a mapping between an arbitrary type and a Java class. There are the following prerequisites for doing this:
You must know the storage layout of the SQL type that you map.
The Java class that you map must implement the
java.sql.SQLDatainterface.
H.6.7.1. Mapping Existing SQL Type to Java Class #
This example shows how to map the Postgres Pro geometric point type to a Java class. A point is stored as two float8 values: the x and y coordinates.
Once the layout of the point type is known, you can create the java.sql.SQLData implementation that uses the java.sql.SQLInput class to read data and the java.sql.SQLOutput class to write data.
package org.postgresql.pljava.example;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class Point implements SQLData {
private double m_x;
private double m_y;
private String m_typeName;
public String getSQLTypeName() {
return m_typeName;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException {
m_x = stream.readDouble();
m_y = stream.readDouble();
m_typeName = typeName;
}
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeDouble(m_x);
stream.writeDouble(m_y);
}
/* Meaningful code that actually does something with this type was
* intentionally left out
*/
}
Finally, install the type mapping using the add_type_mapping command:
SELECT sqlj.add_type_mapping('point', 'org.postgresql.pljava.example.Point');
Now you can use this new class. pljava maps any point parameter to the org.postgresql.pljava.example.Point class.
H.6.7.2. Creating Composite UDT and Mapping It to Java Class #
Here is an example of a complex type created as a composite user-defined type.
CREATE TYPE javatest.complextuple AS (x float8, y float8);
SELECT sqlj.add_type_mapping('javatest.complextuple',
'org.postgresql.pljava.example.ComplexTuple');
package org.postgresql.pljava.example;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class ComplexTuple implements SQLData {
private double m_x;
private double m_y;
private String m_typeName;
public String getSQLTypeName()
{
return m_typeName;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
m_typeName = typeName;
m_x = stream.readDouble();
m_y = stream.readDouble();
}
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeDouble(m_x);
stream.writeDouble(m_y);
}
/* Meaningful code that actually does something with this type was
* intentionally left out
*/
}
H.6.7.3. Generating SQL Automatically #
SQL shown above for this example will be written by the Java compiler if the ComplexTuple class is annotated as a “mapped user-defined type” with the desired SQL name and structure.
@MappedUDT(schema="javatest", name="complextuple",
structure={"x float8", "y float8"})
public class ComplexTuple implements SQLData {
...
Generating SQL reduces the burden of maintaining definitions in two places.
H.6.8. Creating Scalar User-Defined Type #
This text assumes that you have some familiarity with how scalar types are created and added to the Postgres Pro type system. For more information, refer to User-Defined Types.
Creating a new scalar type using Java functions is very similar to how they are created using C functions from an SQL perspective but different when looking at the actual implementation. Java stipulates that a mapping between a Java class and a corresponding SQL type should be done using the java.sql.SQLData, java.sql.SQLInput, and java.sql.SQLOutput interfaces, which are used by pljava. In addition, the Postgres Pro type system stipulates that each type must have a textual representation.
The example below shows how to create a type called javatest.complex. The name of the corresponding Java class will be org.postgresql.pljava.example.ComplexScalar.
The Java class for a scalar UDT must implement the java.sql.SQLData interface. In addition, it must also implement the parse() method that creates and returns an instance of the class and the toString() method that returns something that the parse() method can parse.
package org.postgresql.pljava.example;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
import java.util.logging.Logger;
import org.postgresql.pljava.annotation.Function;
import org.postgresql.pljava.annotation.SQLType;
import org.postgresql.pljava.annotation.BaseUDT;
import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
import static
org.postgresql.pljava.annotation.Function.OnNullInput.RETURNS_NULL;
@BaseUDT(schema="javatest", name="complex",
internalLength=16, alignment=BaseUDT.Alignment.DOUBLE)
public class ComplexScalar implements SQLData
{
private double m_x;
private double m_y;
private String m_typeName;
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
public static ComplexScalar parse(String input, String typeName)
throws SQLException
{
try
{
StreamTokenizer tz = new StreamTokenizer(new StringReader(input));
if(tz.nextToken() == '('
&& tz.nextToken() == StreamTokenizer.TT_NUMBER)
{
double x = tz.nval;
if(tz.nextToken() == ','
&& tz.nextToken() == StreamTokenizer.TT_NUMBER)
{
double y = tz.nval;
if(tz.nextToken() == ')')
{
return new ComplexScalar(x, y, typeName);
}
}
}
throw new SQLException("Unable to parse complex from string \""
+ input + '"');
}
catch(IOException e)
{
throw new SQLException(e.getMessage());
}
}
public ComplexScalar()
{
}
public ComplexScalar(double x, double y, String typeName)
{
m_x = x;
m_y = y;
m_typeName = typeName;
}
@Override
public String getSQLTypeName()
{
return m_typeName;
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
m_x = stream.readDouble();
m_y = stream.readDouble();
m_typeName = typeName;
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeDouble(m_x);
stream.writeDouble(m_y);
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public String toString()
{
s_logger.info(m_typeName + " toString");
StringBuffer sb = new StringBuffer();
sb.append('(');
sb.append(m_x);
sb.append(',');
sb.append(m_y);
sb.append(')');
return sb.toString();
}
/* Meaningful code that actually does something with this type was
* intentionally left out
*/
}
The class itself is annotated with @BaseUDT giving its SQL schema, name, as well as the length and alignment needed for its internal stored form.
Because the compiler knows that the class is a BaseUDT, it expects the parse(), toString(), readSQL(), and writeSQL() methods to be present and will generate correct SQL to declare them as functions to Postgres Pro. The @Function annotations are only there to declare immutability and on-null-input behavior for those methods, because those values are not the defaults when declaring a function.
H.6.9. Returning Complex Types #
pljava handles a complex return value as the IN or OUT parameter. If you declare a function that returns a complex type, you will need to use a Java method with the boolean return type and with the last parameter of the java.sql.ResultSet type added after all of visible method parameters. The output parameter will be initialized to an updatable ResultSet that contains exactly one row.
CREATE FUNCTION createComplexTest(int, int) RETURNS complexTest AS 'foo.fee.Fum.createComplexTest' IMMUTABLE LANGUAGE java;
The pljava method resolver will now find the following method in the foo.fee.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 parameter should be considered as a valid tuple (true) or NULL (false).
H.6.10. Set-Returning Functions #
Returning sets is tricky. You do not need to first build a set and then return it, since large sets require excessive resources. It is better to produce one row at a time. Incidentally, that is exactly what the Postgres Pro backend expects from a function that returns SETOF <type>. The <type> can be a scalar type, such as int, float, or varchar, can be a complex type, or the RECORD type.
H.6.10.1. Returning Set of Scalar Type #
In order to return a set of a scalar type, you need create a Java method that returns an implementation of the java.util.Iterator interface.
CREATE FUNCTION javatest.getNames() RETURNS SETOF varchar AS 'foo.fee.Bar.getNames' IMMUTABLE LANGUAGE java;
The corresponding Java class:
package foo.fee;
import java.util.Iterator;
import org.postgresql.pljava.annotation.Function;
import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
public class Bar
{
@Function(schema="javatest", effects=IMMUTABLE)
public static Iterator<String> getNames()
{
ArrayList<String> names = new ArrayList<>();
names.add("Lisa");
names.add("Bob");
names.add("Bill");
names.add("Sally");
return names.iterator();
}
}
H.6.10.2. Returning Set of Complex Type #
A method returning a set of a complex type must use either the org.postgresql.pljava.ResultSetProvider or org.postgresql.pljava.ResultSetHandle interface. The reason for having two interfaces is that they cater for optimal handling of two distinct use cases. The former is great when you want to dynamically create each row that is to be returned from the SETOF function. The latter makes sense when you want to return the result of an executed query.
H.6.10.2.1. Using ResultSetProvider Interface #
This interface has two methods: boolean assignRowValues(java.sql.ResultSet tupleBuilder, int rowNumber) and void close(). The Postgres Pro query evaluator will call the assignRowValues method repeatedly until it returns false or until the evaluator decides that it does not need any more rows. It will then call the close method.
You can use this interface the following way:
CREATE FUNCTION javatest.listComplexTests(int, int) RETURNS SETOF complexTest AS 'foo.fee.Fum.listComplexTest' IMMUTABLE LANGUAGE java;
The function maps to a static Java method that returns an instance that implements the ResultSetProvider interface.
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 reaching 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 void close()
{
// Nothing needed in this example
}
@Function(effects=IMMUTABLE, schema="javatest", type="complexTest")
public static ResultSetProvider listComplexTests(int base, int increment)
throws SQLException
{
return new Fum(base, increment);
}
}
The listComplexTests(int base, int increment) method is called once. It may return NULL if no results are available or an instance of ResultSetProvider. Here the Fum class implements this interface, so it returns an instance of itself. The assignRowValues(ResultSet receiver, int currentRow) method will then be called repeatedly until it returns false. At that time, close will be called.
The currentRow parameter can be useful in some cases and unnecessary in others. It will be passed as 0 on the first call and incremented by 1 on each subsequent call. If ResultSetProvider is returning results from some source (like Iterator) that remembers its own position, it can simply ignore currentRow.
H.6.10.2.2. Using ResultSetHandle Interface #
This interface is similar to the ResultSetProvider interface in that it has the close method that will be called at the end. But instead of having the evaluator call to a method that builds one row at a time, this interface has the method that returns ResultSet. The query evaluator will iterate over this set and deliver its contents, one tuple at a time, until the call to next returns false or the evaluator decides that no more rows are needed.
Here is an example that executes a query using a statement that it obtained using the default connection. The SQL looks like this:
CREATE FUNCTION javatest.listSupers() RETURNS SETOF pg_user AS 'org.postgresql.pljava.example.Users.listSupers' LANGUAGE java; CREATE FUNCTION javatest.listNonSupers() RETURNS SETOF pg_user AS 'org.postgresql.pljava.example.Users.listNonSupers' LANGUAGE java;
And here is the Java code:
public class Users implements ResultSetHandle
{
private final String m_filter;
private Statement m_statement;
public Users(String filter)
{
m_filter = filter;
}
public ResultSet getResultSet()
throws SQLException
{
m_statement = DriverManager.getConnection("jdbc:default:connection")
.createStatement();
return m_statement.executeQuery("SELECT * FROM pg_user WHERE " + m_filter);
}
public void close()
throws SQLException
{
m_statement.close();
}
@Function(schema="javatest", type="pg_user")
public static ResultSetHandle listSupers()
{
return new Users("usesuper = true");
}
@Function(schema="javatest", type="pg_user")
public static ResultSetHandle listNonSupers()
{
return new Users("usesuper = false");
}
}
H.6.11. Using JDBC #
pljava contains a JDBC driver that maps to the Postgres Pro SPI functions. A connection that maps to the current transaction can be obtained using the following statement:
Connection conn = DriverManager.getConnection("jdbc:default:connection");
Now you can prepare and execute statements just like with any other JDBC connection. There are a couple of limitations:
The transaction cannot be managed in any way. Thus, you cannot use methods on the connection such as:
commit()rollback()setAutoCommit()setTransactionIsolation()
A savepoint cannot outlive the function in which it was set and it must also be rolled back or released by that same function.
ResultSets returned fromexecuteQuery()are alwaysFETCH_FORWARDandCONCUR_READ_ONLY.CallableStatement(for stored procedures) is not yet implemented.Clob/Blobtypes need more work.byte[]andStringwork fine forbytea/textrespectively. A more efficient mapping is planned where the actual array is not copied.
H.6.12. Exception Handling #
You can catch and handle an exception in the Postgres Pro backend just like any other exception. The backend ErrorData structure is exposed as a property in the ServerException class derived from java.sql.SQLException, and the Java try/catch mechanism is synchronized with the backend mechanism.
Note
For several reasons, referring to ServerException and ErrorData from your code is not currently recommended and may become impossible in the future. An improved mechanism is expected in future releases. Until then, using only the standard Java API of java.sql.SQLException and its standard attributes (such as SQLState) is recommended wherever possible.
pljava will always catch exceptions that you do not. They will cause a Postgres Pro error and the message is logged using the Postgres Pro logging utilities. The stack trace of the exception will also be printed if the log_min_messages configuration parameter is set to DEBUG1 or lower.
Note
You will not be able to continue executing backend functions until your function returns and the error is propagated when the backend throws an exception unless you used a savepoint. When a savepoint is rolled back, the exceptional condition is reset and execution can continue.
H.6.13. Savepoints #
Postgres Pro savepoints are exposed using the standard setSavepoint() and releaseSavepoint() methods of the java.sql.Connection interface. The following restrictions apply:
A savepoint must be rolled back or released in the function where it was set.
A savepoint must not outlive the function where it was set.
“Function” here refers to the pljava function that is called from SQL. The restrictions do not prevent the Java code from being organized into several methods but the savepoint cannot survive after the eventual return from Java to the SQL caller.
H.6.14. Logging #
pljava uses the standard java.util.logging.Logger class. Hence, you can write things like:
Logger.getAnonymousLogger().info(
"Time is " + new Date(System.currentTimeMillis()));
At present, Logger is hardwired to a handler that maps the level in the log_min_messages configuration parameter to a valid Logger level and that outputs all messages using the ereport() backend function.
Importantly, Logger methods can quickly discard any message logged at a finer level than the one that was mapped from the Postgres Pro parameter at the time pljava was first used in the current session. Such messages never even get as far as ereport() even if the Postgres Pro parameter is changed later.
So, if expected messages from Java code are not showing up, be sure that the Postgres Pro parameters are fine enough at the time of the first use of pljava in the session, so that Java will not throw the messages away. Once pljava started, the parameters can be changed as desired and will control in the usual way what ereport() does with the messages pljava delivers to it.
The cutoff level in Java is set based on the finer of log_min_messages and client_min_messages.
The following mapping applies between the Logger levels and the Postgres Pro backend levels:
Table H.12. Logger Level Mapping
| java.util.logging.Level | Postgres Pro level |
|---|---|
| SEVERE | ERROR |
| WARNING | WARNING |
| INFO | INFO |
| FINE | DEBUG1 |
| FINER | DEBUG2 |
| FINEST | DEBUG3 |
H.6.15. SQLJ Functions #
-
sqlj.install_jar# Loads a JAR file from a location appointed by an URL into the SQLJ repository. It is an error if a JAR file with the given name already exists in the repository.
Usage:
SELECT sqlj.install_jar(<
jar_url>, <jar_name>, <deploy>);Parameters:
jar_url: The URL that denotes the location of the JAR file that should be loaded.jar_name: The name by which this JAR file can be referenced once it was loaded.deploy:trueif the JAR file should be deployed according to a deployment descriptor,falseotherwise.
-
sqlj.replace_jar# Replaces a loaded JAR file with another JAR file. Use it to update already loaded files. It is an error if the JAR file is not found.
Usage:
SELECT sqlj.replace_jar(<
jar_url>, <jar_name>, <redeploy>);Parameters:
jar_url: The URL that denotes the location of the JAR file that should be loaded.jar_name: The name of the JAR file to be replaced.redeploy:trueif the JAR file should be undeployed according to the deployment descriptor of the old JAR file and deployed according to the deployment descriptor of the new JAR file,falseotherwise.
-
sqlj.remove_jar# Drops the JAR file from the JAR repository. Any
classpaththat references this JAR file is updated accordingly. It is an error if the JAR file is not found.Usage:
SELECT sqlj.remove_jar(<
jar_name>, <undeploy>);Parameters:
jar_name: The name of the JAR file to be removed.undeploy:trueif the JAR file should be undeployed according to the deployment descriptor,falseotherwise.
-
sqlj.get_classpath# Returns the
classpaththat was defined for the given schema.NULLis returned if the schema has noclasspath. It is an error if the given schema does not exist.Usage:
SELECT sqlj.get_classpath(<
schema>);Parameters:
schema: The name of the schema.
-
sqlj.set_classpath# Defines a
classpathfor the given schema. Aclasspathconsists of a colon-separated list of JAR names. It is an error if the given schema does not exist or if one or more JAR names reference nonexistent JAR files.Usage:
SELECT sqlj.set_classpath(<
schema>, <classpath>);Parameters:
schema: The name of the schema.classpath: The colon-separated list of JAR names.
-
sqlj.add_type_mapping# Installs a mapping between a SQL type and a Java class. Once the mapping is in place, parameters and return values are mapped accordingly. Read Mapping SQL Type to Java Class for detailed information.
Usage:
SELECT sqlj.add_type_mapping(<
sql_type>, <java_class>);Parameters:
sql_type: The name of the SQL type. The name can be qualified with a schema (namespace). If the schema is omitted, it is resolved according to the current value of thesearch_pathparameter.java_class: The name of the class. The class must be found in theclasspathin effect for the current schema.
-
sqlj.drop_type_mapping# Removes a mapping between a SQL type and a Java class.
Usage:
SELECT sqlj.drop_type_mapping(<
sql_type>);Parameters:
sql_type: The name of the SQL type. The name can be qualified with a schema (namespace). If the schema is omitted, it is resolved according to the current value of thesearch_pathparameter.
Note
The install_jar and replace_jar functions accept a URL (that must be reachable from the server) to a JAR file. It is even possible, using the rules for URLs of JAR files, to construct one that refers to a JAR file within another JAR file. For example:
jar:file:outer.jar!/inner.jar
However, Java caching of the “outer” JAR file may frustrate attempts to replace or reload a newer version within the same session.
H.6.16. Configuration Parameters #
Several configuration parameters can affect pljava operation, including some common Postgres Pro parameters, as well as own parameters of pljava.
H.6.16.1. Postgres Pro Parameters #
-
check_function_bodies# Affects how strictly pljava validates a new function at the time of
CREATE FUNCTIONexecution or when installing a JAR file withCREATE FUNCTIONamong its deployment actions. Withcheck_function_bodiesset toon, pljava makes sure that the referenced class and method can be loaded and resolved. If the referenced class depends on classes in other JAR files, those other JAR files must be already installed and specified in theclasspath, so loading JAR files with dependencies in the wrong order can incur validation errors. Withcheck_function_bodiesset tooff, only basic syntax is checked atCREATE FUNCTIONtime, so it is possible to declare functions or install JAR files in any order postponing any errors about unresolved dependencies until later when the functions are used.-
dynamic_library_path# Influences where native pljava code objects can be found if the full path is not given to the
LOADcommand.-
server_encoding# Affects all text/character strings exchanged between Postgres Pro and Java.
UTF8as the database and server encoding is strongly recommended. If a different encoding is used, it should be any of the available fully defined character encodings. In particular, the Postgres ProSQL_ASCIIpseudo-encoding does not fully define what any values outside ASCII represent, it is usable but has limitations.
H.6.16.2. pljava Parameters #
-
pljava.allow_unenforced# Only used when pljava is run with no policy enforcement, this parameter is a list of language names (such as
javauandjava) in which functions will be allowed to execute. This parameter has an empty default and should be changed carefully.-
pljava.allow_unenforced_udt# Only used when pljava is run with no policy enforcement, this parameter controls whether data conversion functions associated with pljava mapped user-defined types are allowed to execute. This parameter defaults to
offand should be changed carefully.-
pljava.enable# Setting this parameter to
offprevents pljava startup from completing until the parameter is later set toon. It can be useful for debugging purposes.-
pljava.implementors# A list of “implementor names” that pljava recognizes when processing deployment descriptors inside a JAR file being installed or removed. Deployment descriptors can contain commands with no implementor name, which will be executed always, or with an implementor name executed only on a system recognizing that name. By default, this list contains only the
postgresqlentry. The deployment descriptor that contains commands with other implementor names can achieve a rudimentary kind of conditional execution if earlier commands adjust this list of names. Commas separate elements of this list. Elements that are not regular identifiers need to be surrounded by double-quotes.-
pljava.java_thread_pg_entry# A choice of
allow,error,block, orthrowcontrolling pljava thread management. Java makes heavy use of threading, while Postgres Pro may not be accessed by multiple threads concurrently. Historical behavior of pljava isallow, which serializes access by Java threads into Postgres Pro allowing a different Java thread in only when the current one calls or returns into Java. pljava formerly made some use of Java object finalizers, which required this approach, as finalizers run in their own thread.pljava itself no longer requires the ability for any thread to access Postgres Pro other than the original main thread. User code developed for pljava, however, may still rely on that ability. To test whether it does, the
errororthrowvalue can be used here, and any attempt by a Java thread other than the main one to enter Postgres Pro incurs an exception (and stack trace written to a standard error channel of the server). When confident that there is no code that will need to enter Postgres Pro except on the main thread, theblockvalue can be used. That will eliminate pljava frequent lock acquisitions and releases when the main thread crosses between Postgres Pro and Java and will simply indefinitely block any other Java thread that attempts to enter Postgres Pro. This is an efficient value but can lead to blocked threads or a deadlocked backend if used with code that does attempt to access Postgres Pro from more than one thread.The
throwvalue is likeerrorbut more efficient. Under theerrorvalue, attempted entry by the wrong thread is detected in the native C code only after a lock operation and call through JNI. Under thethrowvalue, the lock operations are elided and an entry attempt by the wrong thread results in no JNI call and an exception thrown directly in Java.-
pljava.libjvm_location# Used by pljava to load the Java runtime. The full path to a
libjvmshared object. The version of the Java library pointed to by this parameter determines whether pljava can run with security policy enforcement or with no policy enforcement.-
pljava.module_path# The module path to be passed to the Java application class loader. The default is computed from the Postgres Pro configuration and is usually correct, unless pljava files were installed in unusual locations. If the path must be set explicitly, there must be at least two (and usually only two) entries: the JAR file with the pljava API and the JAR file with pljava internals.
-
pljava.policy_urls# Only used when pljava is running with security policy enforcement. When running with no policy enforcement, this parameter is ignored. It is a list of URLs to Java security policy files determining the permissions available to pljava functions. Each URL should be enclosed in double quotes; any double quote that is literally part of the URL may be represented as two double quotes (in SQL style) or as
%22in the URL convention. Between double-quoted URLs, a comma is the list delimiter.The
java.securityfile of the Java installation usually defines the following policy file locations:A systemwide policy from the Java vendor sufficient for the Java runtime itself to function as expected.
A per-user location, where a policy file, if found, can add to the policy from the systemwide file.
The list in
pljava.policy_urlsmodifies the list from the Java installation, by default after the first entry, keeping the Java-supplied systemwide policy but replacing the customary per-user file (there probably is not one in the home of thepostgresuser, and if there is it is probably not tailored for pljava).Any entry in this list can start with
n =(inside the quotes) for a positive integernto specify which entry of Java policy location list it replaces (1corresponds to the systemwide policy,2— to the customary user file). URLs not prefixed withn =follow consecutively. If the first entry is not so prefixed,2=is assumed.A final entry of
=(in the required double quotes) prevents use of any remaining entries in the Java site-configured list.This parameter defaults to
"file:${org.postgresql.sysconfdir}/pljava.policy","=".-
pljava.release_lingering_savepoints# How a return value from a pljava function treats any savepoints created within it that was explicitly either released (the savepoint analog of “committed”) or rolled back. If
off(default), they are rolled back. Ifon, they are released/committed. If possible, rather than setting this parameter toon, it would be safer to fix the function to release its own savepoints when appropriate.-
pljava.statement_cache_size# The number of most recently prepared statements pljava can keep open.
-
pljava.vmoptions# Any parameters to be passed to the Java runtime in the same form as the documented parameters for the
javacommand. The string is split on whitespace unless found between single or double quotes. A backslash treats the following character literally but the backslash itself remains in the string, so not all values can be expressed with these rules. If the server encoding is notUTF8, only ASCII characters should be used inpljava.vmoptions.