diff --git a/appveyor.yml b/appveyor.yml index acdea6078..56524ef64 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,12 +33,12 @@ environment: - SYS: MSVC JDK: 21 PG: 13 - - SYS: MSVC - JDK: 21 - PG: 12 - - SYS: MSVC - JDK: 11 - PG: 9.6 +# - SYS: MSVC +# JDK: 21 +# PG: 12 +# - SYS: MSVC +# JDK: 11 +# PG: 9.6 before_build: - ps: .appveyor/appveyor_download_java.ps1 - set JAVA_HOME=%ProgramFiles%\Java\jdk%JDK% diff --git a/pljava-api/src/main/java/module-info.java b/pljava-api/src/main/java/module-info.java index fbfcb8bd4..d501d86a9 100644 --- a/pljava-api/src/main/java/module-info.java +++ b/pljava-api/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Tada AB and other contributors, as listed below. + * Copyright (c) 2020-2023 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -21,14 +21,21 @@ requires transitive java.compiler; exports org.postgresql.pljava; + exports org.postgresql.pljava.adt; + exports org.postgresql.pljava.adt.spi; exports org.postgresql.pljava.annotation; + exports org.postgresql.pljava.model; exports org.postgresql.pljava.sqlgen; exports org.postgresql.pljava.annotation.processing to org.postgresql.pljava.internal; + uses org.postgresql.pljava.Adapter.Service; + uses org.postgresql.pljava.Session; + uses org.postgresql.pljava.model.CatalogObject.Factory; + provides javax.annotation.processing.Processor with org.postgresql.pljava.annotation.processing.DDRProcessor; } diff --git a/pljava-api/src/main/java/org/postgresql/pljava/Adapter.java b/pljava-api/src/main/java/org/postgresql/pljava/Adapter.java new file mode 100644 index 000000000..6783d7275 --- /dev/null +++ b/pljava-api/src/main/java/org/postgresql/pljava/Adapter.java @@ -0,0 +1,2040 @@ +/* + * Copyright (c) 2022-2025 Tada AB and other contributors, as listed below. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the The BSD 3-Clause License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/BSD-3-Clause + * + * Contributors: + * Chapman Flack + */ +package org.postgresql.pljava; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.collectArguments; +import static java.lang.invoke.MethodHandles.dropArguments; +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodHandles.permuteArguments; +import java.lang.invoke.MethodType; +import static java.lang.invoke.MethodType.methodType; + +import static java.lang.reflect.Array.newInstance; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +import java.security.Permission; +import java.security.PermissionCollection; + +import java.sql.SQLException; +import java.sql.SQLDataException; + +import java.util.Arrays; +import static java.util.Arrays.stream; +import static java.util.Collections.emptyEnumeration; +import static java.util.Collections.enumeration; +import java.util.Enumeration; +import java.util.List; +import static java.util.Objects.requireNonNull; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.postgresql.pljava.adt.spi.AbstractType; +import org.postgresql.pljava.adt.spi.AbstractType.Bindings; +import org.postgresql.pljava.adt.spi.AbstractType.MultiArray; +import org.postgresql.pljava.adt.spi.Datum; +import org.postgresql.pljava.adt.spi.TwosComplement; + +import org.postgresql.pljava.model.Attribute; +import org.postgresql.pljava.model.RegType; +import org.postgresql.pljava.model.TupleTableSlot.Indexed; + +import org.postgresql.pljava.model.SlotTester.Visible; // temporary for test jig + +import static org.postgresql.pljava.adt.spi.AbstractType.erase; +import static org.postgresql.pljava.adt.spi.AbstractType.isSubtype; +import static org.postgresql.pljava.adt.spi.AbstractType.refine; +import static org.postgresql.pljava.adt.spi.AbstractType.specialization; +import static org.postgresql.pljava.adt.spi.AbstractType.substitute; + +/** + * Base for classes that implement data types over raw PostgreSQL datums. + *
+ * A PL/Java data type adapter is a concrete subclass of this class that knows + * the structure of one or more PostgreSQL data types and can convert between + * their raw {@code Datum} form and an appropriate Java class or primitive type + * for use in PL/Java code. It will use the {@code Via...} enum declared here + * (to indicate how it will access the PostgreSQL {@code Datum}), and extend + * an {@code As...} abstract class declared here (to indicate the supported + * Java reference or primitive type). + *
+ * An adapter should be stateless and thread-safe. There should be no need to + * instantiate more than one instance of an adapter for a given type mapping. + *
+ * An adapter has a "top" type T, indicating the type it will present to client + * code, and an "under" type U, which client code can generally wildcard and + * ignore; an implementing class that can be composed over another adapter uses + * U to indicate what that "under" adapter's "top" type must be. The Java + * compiler records enough information for both parameters to allow PL/Java to + * reconstruct the type relationships in a stack of composed adapters. + *
+ * An implementing leaf adapter (which will work directly on PostgreSQL Datum + * rather than being composed over another adapter) can declare {@code Void} + * for U by convention. An adapter meant to be composed over another, where the + * "under" adapter has a primitive type, can declare the primitive type's boxed + * counterpart as U. + *
+ * For a primitive-typed adapter, the "top" type is implicit in the class name
+ * {@code AsLong}, {@code AsInt}, and so on, and the "under" type follows as the
+ * parameter U. For ease of reading, the type parameters of the two-parameter
+ * classes like {@code As
+ * The precise meaning of the "top" type T depends on whether an adapter is
+ * an instance of {@code As
+ * To preserve type safety, only recognized "leaf" adapters (those registered
+ * to {@link #configure configure} with a non-null {@link Via via})
+ * will be able to manipulate raw {@code Datum}s. An adapter class
+ * should avoid leaking a {@code Datum} to other code.
+ */
+public abstract class Adapter
+ * It can be invoked that way from {@code As} for array adapters; otherwise,
+ * the subclass constructors all declare the parameter as {@code Class
+ * The adapter and contract here are raw types. The accessible subclass
+ * constructors will constrain their type arguments to be compatible.
+ */
+ private Adapter(
+ Configuration configuration, Adapter over, Contract using, Type witness)
+ {
+ requireNonNull(configuration,
+ () -> getClass() + " instantiated without a Configuration object");
+ if ( getClass() != configuration.m_class )
+ throw new IllegalArgumentException(
+ getClass() + " instantiated with a Configuration object " +
+ "for the wrong class");
+
+ if ( configuration instanceof Configuration.Leaf )
+ {
+ if ( null != over )
+ throw new IllegalArgumentException(
+ getClass() + " instantiated with non-null 'over' but is " +
+ "a leaf adapter");
+
+ Configuration.Leaf leaf = (Configuration.Leaf)configuration;
+
+ Type top = leaf.m_top;
+ /*
+ * If instantiated with a subclass of Contract, the type with
+ * which it specializes Contract may tell us more than our top
+ * type precomputed at configuration.
+ */
+ if ( null != using )
+ {
+ if ( witness instanceof TypeWrapper )
+ {
+ top = ((TypeWrapper)witness).wrapped;
+ witness = null;
+ }
+ else
+ top = specialization(using.getClass(), Contract.class)[0];
+ }
+
+ MethodHandle mh = leaf.m_fetch.bindTo(this);
+
+ @SuppressWarnings("unchecked")
+ Class
+ * At this level, an adapter is free to use {@code Via.CHAR} and treat
+ * {@code char} internally as a 16-bit unsigned integral type with no other
+ * special meaning. If an adapter will return an unsigned 16-bit
+ * type, it should extend either {@code AsShort.Unsigned} or {@code AsChar},
+ * based on whether the value it returns represents UTF-16 character data.
+ */
+ protected enum Via
+ {
+ DATUM ( Datum.Input.class, "getDatum"),
+ INT64SX ( long.class, "getLongSignExtended"),
+ INT64ZX ( long.class, "getLongZeroExtended"),
+ DOUBLE ( double.class, "getDouble"),
+ INT32SX ( int.class, "getIntSignExtended"),
+ INT32ZX ( int.class, "getIntZeroExtended"),
+ FLOAT ( float.class, "getFloat"),
+ SHORT ( short.class, "getShort"),
+ CHAR ( char.class, "getChar"),
+ BYTE ( byte.class, "getByte"),
+ BOOLEAN ( boolean.class, "getBoolean");
+
+ Via(Class> type, String method)
+ {
+ try
+ {
+ MethodHandle h;
+ h = lookup().findVirtual(Datum.Accessor.class, method,
+ type.isPrimitive()
+ ? methodType(
+ type, Object.class, int.class)
+ : methodType(
+ type, Object.class, int.class, Attribute.class));
+
+ if ( type.isPrimitive() )
+ h = dropArguments(h, 3, Attribute.class);
+
+ m_handle = h;
+ }
+ catch ( ReflectiveOperationException e )
+ {
+ throw wrapped(e);
+ }
+ }
+
+ MethodHandle m_handle;
+ }
+
+ @Override
+ public String toString()
+ {
+ Class> c = getClass();
+ Module m = c.getModule();
+ return
+ c.getModule().getName() + "/" +
+ c.getCanonicalName().substring(1 + c.getPackageName().length() ) +
+ " to produce " + topType();
+ }
+
+ /**
+ * Method that a leaf {@code Adapter} must implement to indicate whether it
+ * is capable of fetching a given PostgreSQL type.
+ *
+ * In a composing adapter, this default implementation delegates to
+ * the adapter beneath.
+ * @throws UnsupportedOperationException if called in a leaf adapter
+ */
+ public boolean canFetch(RegType pgType)
+ {
+ if ( null != m_underAdapter )
+ return m_underAdapter.canFetch(pgType);
+ throw new UnsupportedOperationException(
+ toString() + " is a leaf adapter and does not override canFetch");
+ }
+
+ /**
+ * Method that an {@code Adapter} may override to indicate whether it
+ * is capable of fetching a given PostgreSQL attribute.
+ *
+ * If not overridden, this implementation delegates to the adapter beneath,
+ * if composed; in a leaf adapter, it delegates to
+ * {@link #canFetch(RegType) canFetch} for the attribute's declared
+ * PostgreSQL type.
+ */
+ public boolean canFetch(Attribute attr)
+ {
+ if ( null != m_underAdapter )
+ return m_underAdapter.canFetch(attr);
+ return canFetch(attr.type());
+ }
+
+ /**
+ * Method that an {@code Adapter} must implement to indicate whether it
+ * is capable of returning some usable representation of SQL null values.
+ *
+ * An {@code Adapter} that cannot should only be used with values that
+ * are known never to be null; it will throw an exception if asked to fetch
+ * a value that is null.
+ *
+ * An adapter usable with null values can be formed by composing, for
+ * example, an adapter producing {@code Optional} over an adapter that
+ * cannot fetch nulls.
+ */
+ public abstract boolean canFetchNull();
+
+ /**
+ * A static method to indicate the type returned by a given {@code Adapter}
+ * subclass, based only on the type information recorded for it by the Java
+ * compiler.
+ *
+ * The type returned could contain free type variables that may be given
+ * concrete values when the instance {@link #topType() topType} method is
+ * called on a particular instance of the class.
+ *
+ * When cls is a subclass of {@code Primitive}, this method
+ * returns the {@code Class} object for the actual primitive type,
+ * not the boxed type.
+ */
+ public static Type topType(Class extends Adapter> cls)
+ {
+ Type[] params = specialization(cls, Adapter.class);
+ if ( null == params )
+ throw new IllegalArgumentException(
+ cls + " does not extend Adapter");
+ Type top = params[0];
+ if ( Primitive.class.isAssignableFrom(cls) )
+ {
+ top = methodType((Class>)top).unwrap().returnType();
+ assert ((Class>)top).isPrimitive();
+ }
+ return top;
+ }
+
+ /**
+ * The full generic {@link Type Type} this Adapter presents to Java.
+ *
+ * Unlike the static method, this instance method, on an adapter formed
+ * by composition, returns the actual type obtained by unifying
+ * the "under" adapter's top type with the top adapter's "under" type, then
+ * making the indicated substitutions in the top adapter's "top" type.
+ *
+ * Likewise, for an adapter constructed with an array contract and an
+ * adapter for the element type, the element adapter's "top" type is unified
+ * with the contract's element type, and this method returns the contract's
+ * result type with the same substitutions made.
+ */
+ public Type topType()
+ {
+ return m_topType;
+ }
+
+ /**
+ * A static method to indicate the "under" type expected by a given
+ * {@code Adapter} subclass that is intended for composition over another
+ * adapter, based only on the type information recorded for it by the Java
+ * compiler.
+ *
+ * The type returned could contain free type variables.
+ */
+ public static Type underType(Class extends Adapter> cls)
+ {
+ Type[] params = specialization(cls, Adapter.class);
+ if ( null == params )
+ throw new IllegalArgumentException(
+ cls + " does not extend Adapter");
+ return params[1];
+ }
+
+ /**
+ * A class that is returned by the {@link #configure configure} method,
+ * intended for use during an {@code Adapter} subclass's static
+ * initialization, and must be supplied to the constructor when instances
+ * of the class are created.
+ */
+ protected static abstract class Configuration
+ {
+ final Class extends Adapter> m_class;
+ /**
+ * In the case of a primitive-typed adapter, this will really be the
+ * primitive Class object, not the corresponding boxed class.
+ */
+ final Type m_top;
+
+ Configuration(Class extends Adapter> cls, Type top)
+ {
+ m_class = cls;
+ m_top = top;
+ }
+
+ static class Leaf extends Configuration
+ {
+ final MethodHandle m_fetch;
+
+ Leaf(Class extends Adapter> cls, Type top, MethodHandle fetcher)
+ {
+ super(cls, top);
+ m_fetch = fetcher;
+ }
+ }
+
+ static class NonLeaf extends Configuration
+ {
+ /**
+ * For an adapter meant to compose over a primitive-typed one, this
+ * is the actual primitive class object for the under-adapter's
+ * expected return type, not the boxed counterpart.
+ */
+ final Type m_under;
+ final MethodHandle m_adapt;
+
+ NonLeaf(
+ Class extends Adapter> cls, Type top, Type under,
+ MethodHandle fetcher)
+ {
+ super(cls, top);
+ m_under = under;
+ m_adapt = fetcher;
+ }
+ }
+ }
+
+ /**
+ * Throws a security exception if permission to configure an adapter
+ * isn't held.
+ *
+ * For the time being, there is only Permission("*", "fetch"), so this needs
+ * no parameters and can use a static instance of the permission.
+ */
+ @SuppressWarnings("removal") // JEP 411
+ private static void checkAllowed()
+ {
+ Service.CHECKER.accept(Permission.INSTANCE);
+ }
+
+ /**
+ * Method that must be called in static initialization of an {@code Adapter}
+ * subclass, producing a {@code Configuration} object that must be passed
+ * to the constructor when creating an instance.
+ *
+ * If the adapter class is in a named module, its containing package must be
+ * exported to at least {@code org.postgresql.pljava}.
+ *
+ * When a leaf adapter (one that does not compose over some other adapter,
+ * but acts directly on PostgreSQL datums) is configured, the necessary
+ * {@link Permission Permission} is checked.
+ * @param cls The Adapter subclass being configured.
+ * @param via null for a composing (non-leaf) adapter; otherwise a value
+ * of the {@link Via} enumeration, indicating how the underlying PostgreSQL
+ * datum will be presented to the adapter.
+ * @throws SecurityException if the class being configured represents a leaf
+ * adapter and the necessary permission is not held.
+ */
+ protected static Configuration configure(
+ Class extends Adapter> cls, Via via)
+ {
+ Adapter.class.getModule().addReads(cls.getModule());
+ Type top = topType(cls);
+ Type under = underType(cls);
+ Class> topErased = erase(top);
+ Class> underErased = erase(under);
+
+ MethodHandle underFetcher = null;
+ String fetchName;
+ Predicate
+ * By being technically a subclass of {@code Adapter}, the container class
+ * will have access to the protected {@code Configuration} class and
+ * {@code configure} method.
+ */
+ public static abstract class Container extends Adapter
+ * Can be useful when constructing a {@link Contract.Array Contract.Array}
+ * that will inspect metadata for an array (its element type or dimensions)
+ * without fetching any elements.
+ */
+ public static final class Opaque extends As
+ * However, any actual attempt to fetch a non-null value
+ * using the {@code Opaque} adapter will incur
+ * an {@code UnsupportedOperationException}.
+ */
+ @Override
+ public boolean canFetch(RegType pgType)
+ {
+ return true;
+ }
+
+ private Void fetch(
+ Attribute a, Datum.Accessor acc, B buffer, int offset,
+ Attribute aa)
+ {
+ throw new UnsupportedOperationException(
+ "Adapter.Opaque cannot fetch anything");
+ }
+
+ private Opaque(Configuration c)
+ {
+ super(c, null, null);
+ }
+
+ static
+ {
+ try
+ {
+ Lookup lup = lookup();
+ MethodHandle fetcher = lup.findVirtual(
+ Opaque.class, "fetch", methodType(Void.class,
+ Attribute.class, Datum.Accessor.class, Object.class,
+ int.class, Attribute.class));
+ Configuration c =
+ new Configuration.Leaf(Opaque.class, Void.class, fetcher);
+
+ INSTANCE = new Opaque(c);
+ }
+ catch ( ReflectiveOperationException e )
+ {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+ }
+
+ /**
+ * Superclass for adapters that fetch something and return it as a reference
+ * type T.
+ *
+ * The type variable U for the thing consumed gets no enforcement from
+ * the compiler, because any extending adapter class provides its own
+ * {@code T fetch(Attribute,something)} method, with no abstract version
+ * inherited from this class to constrain it. The method will be found
+ * reflectively by name and parameter types, so the "something" only has to
+ * match the type of the accessor method specified with {@code Via}, or the
+ * type returned by an underlying adapter that this one will be composed
+ * over.
+ *
+ * In particular, that means this is the class to extend even if using a
+ * primitive accessor method, or composing over an adapter that returns a
+ * primitive type, as long as this adapter will return a reference type T.
+ * Such an adapter simply declares that it extends {@code As
+ * When Java's reflection methods on generic types are used to compute
+ * the (non-erased) result type of a stack of composed adapters, the type
+ * variable U can be used in relating the input to the output type of each.
+ */
+ public abstract static class As
+ * The contract and element adapter here are raw types. The accessible
+ * subclass constructors will permit only compatible combinations of
+ * parameterized types.
+ */
+ private As(
+ Contract.Array using, Adapter adapter, Type witness,
+ Configuration c)
+ {
+ super(c, null, using,
+ witness != null ? witness : refinement(using, adapter));
+
+ MethodHandle mh = m_fetchHandle;
+ m_fetchHandleErased =
+ mh.asType(mh.type().changeReturnType(Object.class));
+ }
+
+ /**
+ * Returns the type that will be produced by the array contract
+ * using when applied to the element-type adapter
+ * adapter.
+ *
+ * Determined by unifying the contract's element type with
+ * the result type of adapter, then repeating any resulting
+ * substitutions in the contract's result type.
+ */
+ private static Type refinement(Contract.Array using, Adapter adapter)
+ {
+ Type[] unrefined =
+ specialization(using.getClass(), Contract.Array.class);
+ Type result = unrefined[0];
+ Type element = unrefined[1];
+ /*
+ * A Contract that expects a primitive-typed adapter must already be
+ * specialized to one primitive type, so there is nothing to refine.
+ */
+ if ( adapter instanceof Primitive )
+ return result;
+ return refine(adapter.topType(), element, result)[1];
+ }
+
+ /**
+ * Method invoked internally when this {@code Adapter} is used to fetch
+ * a value; not intended for use in application code.
+ */
+ public final T fetch(
+ Datum.Accessor acc, B buffer, int offset, Attribute a)
+ {
+ try
+ {
+ return (T)
+ m_fetchHandleErased.invokeExact(a, acc, buffer, offset, a);
+ }
+ catch ( Throwable t )
+ {
+ throw wrapped(t);
+ }
+ }
+
+ /**
+ * A default implementation of {@code canFetchNull} that unconditionally
+ * returns true.
+ *
+ * An adapter that extends this class, if it does not override
+ * {@link #fetchNull fetchNull}, will simply map any SQL null value
+ * to a Java null.
+ */
+ @Override
+ public boolean canFetchNull()
+ {
+ return true;
+ }
+
+ /**
+ * Determines the value to which SQL null should be mapped.
+ *
+ * If not overridden, this implementation returns Java null.
+ */
+ public T fetchNull(Attribute a)
+ {
+ return null;
+ }
+
+ /**
+ * Allocate an array of the given length with this adapter's
+ * result type as its component type.
+ */
+ @SuppressWarnings("unchecked")
+ public T[] arrayOf(int length)
+ {
+ return (T[])newInstance(m_topErased, length);
+ }
+ }
+
+ /**
+ * Abstract supertype of array adapters.
+ *
+ * Instantiating an array adapter requires supplying an array contract
+ * and a compatible adapter for the element type, to be stored in the
+ * corresponding final fields here, which are declared with raw types.
+ * The several accessible constructors enforce the various compatible
+ * parameterizations for the two arguments.
+ */
+ public abstract static class Array
+ * Can be used when the only statically-known type for an array
+ * is the polymorphic {@link RegType#ANYARRAY ANYARRAY} type,
+ * to determine the actual element type of a given array. A suitable
+ * {@code Adapter} for that type can then be chosen, and used
+ * to construct an array adapter that can access the content
+ * of the array.
+ */
+ public static Array
+ * Declared here as the raw type. The accessible constructors enforce
+ * the compatibility requirements between this and the supplied
+ * element adapter.
+ */
+ protected final Contract.Array m_contract;
+
+ /**
+ * The {@code Adapter} that this array adapter will use for the array's
+ * element type, together with the supplied contract.
+ *
+ * Declared here as the raw type. The accessible constructors enforce
+ * the compatibility requirements between this and the supplied
+ * contract.
+ */
+ protected final Adapter m_elementAdapter;
+
+ /**
+ * Constructor for a leaf array {@code Adapter} that is based on
+ * a {@code Contract.Array} and a reference-returning {@code Adapter}
+ * for the element type.
+ * @param using the array Contract that will be used to produce
+ * the value returned
+ * @param adapter an Adapter producing a representation of the array's
+ * element type
+ * @param witness if not null, the top type the resulting
+ * adapter will produce, if a Type object can specify that more
+ * precisely than the default typing rules.
+ * @param c Configuration instance generated for this class
+ */
+ protected
+ * Subclasses for integral types, namely {@code AsLong}, {@code asInt},
+ * and {@code AsShort}, cannot be extended directly, but only via their
+ * {@code Signed} or {@code Unsigned} nested subclasses, according to how
+ * the value is meant to be used. Nothing can change how Java treats the
+ * primitive types (always as signed), but the {@code Signed} and
+ * {@code Unsigned} subclasses here offer methods for the operations that
+ * differ, allowing the right behavior to be achieved if those methods
+ * are used.
+ *
+ * Whether an adapter extends {@code AsShort.Unsigned} or {@code AsChar}
+ * (also an unsigned 16-bit type) should be determined based on whether
+ * the resulting value is meant to have a UTF-16 character meaning.
+ */
+ public abstract static class Primitive
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public long fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java long", "22002"));
+ }
+
+ /**
+ * Abstract superclass of signed primitive {@code long} adapters.
+ */
+ public abstract static class Signed extends AsLong
+ implements TwosComplement.Signed
+ {
+ protected
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public double fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java double", "22002"));
+ }
+ }
+
+ /**
+ * Abstract superclass of signed and unsigned primitive {@code int}
+ * adapters.
+ */
+ public abstract static class AsInt extends Primitive
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public int fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java int", "22002"));
+ }
+
+ /**
+ * Abstract superclass of signed primitive {@code int} adapters.
+ */
+ public abstract static class Signed extends AsInt
+ implements TwosComplement.Signed
+ {
+ protected
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public float fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java float", "22002"));
+ }
+ }
+
+ /**
+ * Abstract superclass of signed and unsigned primitive {@code short}
+ * adapters.
+ */
+ public abstract static class AsShort extends Primitive
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public short fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java short", "22002"));
+ }
+
+ /**
+ * Abstract superclass of signed primitive {@code short} adapters.
+ */
+ public abstract static class Signed extends AsShort
+ implements TwosComplement.Signed
+ {
+ protected
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public char fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java char", "22002"));
+ }
+ }
+
+ /**
+ * Abstract superclass of signed and unsigned primitive {@code byte}
+ * adapters.
+ */
+ public abstract static class AsByte extends Primitive
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public byte fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java byte", "22002"));
+ }
+
+ /**
+ * Abstract superclass of signed primitive {@code byte} adapters.
+ */
+ public abstract static class Signed extends AsByte
+ implements TwosComplement.Signed
+ {
+ protected
+ * If not overridden, this implementation throws an
+ * {@code SQLDataException} with {@code SQLSTATE 22002},
+ * {@code null_value_no_indicator_parameter}.
+ */
+ public boolean fetchNull(Attribute a)
+ {
+ throw wrapped(new SQLDataException(
+ "SQL NULL cannot be returned as Java boolean", "22002"));
+ }
+ }
+
+ /**
+ * A marker interface to be extended by functional interfaces that
+ * serve as ADT contracts.
+ *
+ * It facilitates the declaration of "dispenser" interfaces by which
+ * one contract can rely on others.
+ * @param
+ * The distinguishing feature is an associated {@code Adapter} handling
+ * the element type of the array-like type. This form of contract may
+ * be useful for range and multirange types as well as for arrays.
+ * @param
+ * The idea may or may not be worth keeping, and either way, this particular
+ * exception might not be part of any final API.
+ */
+ public static class AdapterException extends RuntimeException
+ {
+ AdapterException(String message, Throwable cause)
+ {
+ super(message, cause, true, false);
+ }
+
+ /**
+ * Unwraps this wrapper's cause and returns it, if it is an instance of
+ * the exception type declared; otherwise, just throws this
+ * wrapper again.
+ */
+ public
+ * The proper spelling in a policy file is
+ * {@code org.postgresql.pljava.Adapter$Permission}.
+ *
+ * For the time being, only {@code "*"} is allowed as the name,
+ * and only {@code "fetch"} as the actions.
+ *
+ * Only a "leaf" adapter (one that will interact with PostgreSQL datum
+ * values directly) requires permission. Definition of composing adapters
+ * (those that can be applied over another adapter and transform the Java
+ * values somehow) is unrestricted.
+ */
+ public static final class Permission extends java.security.Permission
+ {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * An instance of this permission (not a singleton, merely one among
+ * possible others).
+ */
+ static final Permission INSTANCE = new Permission("*", "fetch");
+
+ public Permission(String name, String actions)
+ {
+ super("*");
+ if ( ! ( "*".equals(name) && "fetch".equals(actions) ) )
+ throw new IllegalArgumentException(
+ "the only currently-allowed name and actions are " +
+ "* and fetch, not " + name + " and " + actions);
+ }
+
+ @Override
+ public boolean equals(Object other)
+ {
+ return other instanceof Permission;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return 131129;
+ }
+
+ @Override
+ public String getActions()
+ {
+ return "fetch";
+ }
+
+ @Override
+ public PermissionCollection newPermissionCollection()
+ {
+ return new Collection();
+ }
+
+ @Override
+ public boolean implies(java.security.Permission p)
+ {
+ return p instanceof Permission;
+ }
+
+ static class Collection extends PermissionCollection
+ {
+ private static final long serialVersionUID = 1L;
+
+ Permission the_permission = null;
+
+ @Override
+ public void add(java.security.Permission p)
+ {
+ if ( isReadOnly() )
+ throw new SecurityException(
+ "attempt to add a Permission to a readonly " +
+ "PermissionCollection");
+
+ if ( ! (p instanceof Permission) )
+ throw new IllegalArgumentException(
+ "invalid in homogeneous PermissionCollection: " + p);
+
+ if ( null == the_permission )
+ the_permission = (Permission) p;
+ }
+
+ @Override
+ public boolean implies(java.security.Permission p)
+ {
+ if ( null == the_permission )
+ return false;
+ return the_permission.implies(p);
+ }
+
+ @Override
+ public Enumeration
+ * The {@code TypeWrapper} is a contrivance so that the computed array
+ * type can be passed back up through the constructors in a non-racy
+ * way.
+ */
+ protected abstract
+ * Can only be instantiated here, to limit the ability for arbitrary code
+ * to supply computed (or miscomputed) types.
+ *
+ * The implementation layer will call {@link #setWrappedType setWrappedType}
+ * and then pass the wrapper to the appropriate adapter constructor.
+ * @hidden
+ */
+ public static class TypeWrapper implements Type
+ {
+ @Override
+ public String getTypeName()
+ {
+ return "(a PL/Java TypeWrapper)";
+ }
+
+ private Type wrapped;
+
+ private TypeWrapper() { }
+
+ public void setWrappedType(Type t)
+ {
+ wrapped = t;
+ }
+ }
+
+ /**
+ * Mixin allowing properly-typed array adapters of various dimensionalities
+ * to be derived from an adapter for the array component type.
+ *
+ * If a is an adapter producing type T, then
+ * {@code a.a4().a2()} is an {@code ArrayBuilder} that can build a
+ * six-dimensional array adapter producing type T[][][][][][].
+ *
+ * @param
+ * A {@code Lifespan} generalizes over assorted classes that can play that role,
+ * such as PostgreSQL's {@link ResourceOwner ResourceOwner} and
+ * {@link MemoryContext MemoryContext}. {@code MemoryContext} may see the most
+ * use in PL/Java, as the typical reason to scope the lifetime of some PL/Java
+ * object is that it refers to some allocation of native memory.
+ *
+ * The invocation of a PL/Java function is also usefully treated as a resource
+ * owner. It is reasonable to depend on the objects passed in the function call
+ * to remain usable as long as the call is on the stack, if no other explicit
+ * lifespan applies.
+ *
+ * Java's incubating foreign function and memory API will bring a
+ * {@code ResourceScope} object for which some relation to a PL/Java
+ * {@code Lifespan} can probably be defined.
+ *
+ * The history of PostgreSQL MemoryContexts
+ * (the older mechanism, appearing in PostgreSQL 7.1), and ResourceOwners
+ * (introduced in 8.0) is interesting. As the latter's {@code README} puts it,
+ *
+ * Only later, in PostgreSQL 9.5, did {@code MemoryContext} gain a callback
+ * mechanism for detecting reset or delete, with which it also becomes usable
+ * as a kind of lifespan under PL/Java's broadened view of the concept.
+ * While not
+ * An implementing class does not implement this interface directly, but rather
+ * implements one or both of the subinterfaces {@link InlineBlocks InlineBlocks}
+ * and {@link Routines Routines}. A language that implements {@code Routines}
+ * may also implement one or more of: {@link ReturningSets ReturningSets},
+ * {@link Triggers Triggers}, {@link UsingTransforms UsingTransforms}.
+ * The implementing class must have a public constructor with a
+ * {@link ProceduralLanguage ProceduralLanguage} parameter, which it may ignore,
+ * or use to determine the name, oid, accessibility, or other details of the
+ * declared PostgreSQL language the handler class has been instantiated for.
+ */
+public interface PLJavaBasedLanguage
+{
+ /**
+ * To be implemented by a language that supports routines (that is,
+ * functions and/or procedures).
+ *
+ * Whether a routine is a function or procedure can be determined at
+ * validation time ({@code subject.}{@link RegProcedure#kind() kind()} in
+ * {@link #essentialChecks essentialChecks} or
+ * {@link #additionalChecks additionalChecks}) or at
+ * {@link #prepare prepare} time
+ * (({@code target.}{@link RegProcedure#kind() kind()}).
+ * A procedure can also be distinguished from a function in that,
+ * at {@link Routine#call Routine.call(fcinfo)} time, if a procedure is
+ * being called, {@code fcinfo.}{@link Call#context() context()} returns
+ * an instance of {@link Call.Context.CallContext CallContext}.
+ *
+ * A function is always called within an existing transaction; while it may
+ * use subtransactions / savepoints, it can never commit, roll back, or
+ * start a new top-level transaction.
+ *
+ * A procedure is allowed to start, commit, and roll back top-level
+ * transactions, provided it was not called inside an existing explicit
+ * transation. That condition can be checked by consulting
+ * {@link Call.Context.CallContext#atomic() CallContext.atomic()} when
+ * {@code fcinfo.context()} returns an instance of {@code CallContext}.
+ * When {@code atomic()} returns {@code true}, transaction control is not
+ * allowed. (If {@code fcinfo.context()} returns anything other than an
+ * instance of {@code CallContext}, this is not a procedure call, and
+ * transaction control is never allowed.)
+ *
+ * A handler may use this information to impose its own (for example,
+ * compile-time) limits on a routine's access to transaction-control
+ * operations. Any use of SPI by the routine will be appropriately limited
+ * with no need for attention from the handler, as PL/Java propagates the
+ * atomic/nonatomic flag to SPI always.
+ */
+ public interface Routines extends PLJavaBasedLanguage
+ {
+ /**
+ * Performs the essential validation checks on a proposed
+ * PL/Java-based routine.
+ *
+ * This method should check (when checkBody is true) all the
+ * essential conditions that {@link #prepare prepare} may assume have
+ * been checked. Because there is no guarantee that validation at
+ * routine-creation time always occurred, PL/Java's dispatcher will not
+ * only call this method at validation time, but also will never call
+ * {@code prepare} without making sure this method (passing true for
+ * checkBody) has been called first.
+ *
+ * This method should throw an informative exception for any check that
+ * fails, otherwise returning normally. Unless there is a more-specific
+ * choice, {@link SQLSyntaxErrorException} with {@code SQLState}
+ * {@code 42P13} corresponds to PostgreSQL's
+ * {@code invalid_function_definition}.
+ *
+ * Checks that are helpful at routine-creation time, but not essential
+ * to correctness of {@code prepare}, can be made in
+ * {@link #additionalChecks additionalChecks}.
+ *
+ * The dispatcher will never invoke this method for a subject
+ * with {@link RegProcedure#returnsSet returnsSet()} true, so this
+ * method may assume that property is false, unless the language also
+ * implements {@link ReturningSets ReturningSets} and the
+ * {@link ReturningSets#essentialSRFChecks essentialSRFChecks} method
+ * delegates to this one (as its default implementation does).
+ *
+ * If checkBody is false, less-thorough checks may be
+ * needed. The details are left to the language implementation;
+ * in general, basic checks of syntax, matching parameter counts, and
+ * so on are ok, while checks that load or compile user code or depend
+ * on other database state may be better avoided. The validator may be
+ * invoked with checkBody false at times when not all
+ * expected state may be in place, such as during {@code pg_restore}
+ * or {@code pg_upgrade}.
+ *
+ * This method is invoked with checkBody false only if the
+ * JVM has been started and PL/Java has already loaded and instantiated
+ * this language-handler class, or succeeds in doing so. If not, and
+ * checkBody is false, PL/Java simply treats the validation
+ * as successful.
+ *
+ * This default implementation checks nothing.
+ */
+ default void essentialChecks(
+ RegProcedure> subject, PLJavaBased memo, boolean checkBody)
+ throws SQLException
+ {
+ }
+
+ /**
+ * Performs additional validation checks on a proposed PL/Java-based
+ * routine.
+ *
+ * This method should be used for checks that may give helpful feedback
+ * at routine-creation time, but can be skipped at run time because the
+ * correct behavior of {@link #prepare prepare} does not depend on them.
+ * PL/Java calls this method only at routine-creation time, just after
+ * {@link #essentialChecks essentialChecks} has completed normally.
+ *
+ * This method should throw an informative exception for any check that
+ * fails, otherwise returning normally. Unless there is a more-specific
+ * choice, {@link SQLSyntaxErrorException} with {@code SQLState}
+ * {@code 42P13} corresponds to PostgreSQL's
+ * {@code invalid_function_definition}.
+ *
+ * Checks of conditions essential to correctness of {@code prepare}
+ * must be made in {@code essentialChecks}.
+ *
+ * The dispatcher will never invoke this method for a subject
+ * with {@link RegProcedure#returnsSet returnsSet()} true, so this
+ * method may assume that property is false, unless the language also
+ * implements {@link ReturningSets ReturningSets} and the
+ * {@link ReturningSets#additionalSRFChecks additionalSRFChecks} method
+ * delegates to this one (as its default implementation does).
+ *
+ * If checkBody is false, less-thorough checks may be
+ * needed. The details are left to the language implementation;
+ * in general, basic checks of syntax, matching parameter counts, and
+ * so on are ok, while checks that load or compile user code or depend
+ * on other database state may be better avoided. The validator may be
+ * invoked with checkBody false at times when not all
+ * expected state may be in place, such as during {@code pg_restore}
+ * or {@code pg_upgrade}.
+ *
+ * This default implementation checks nothing.
+ */
+ default void additionalChecks(
+ RegProcedure> subject, PLJavaBased memo, boolean checkBody)
+ throws SQLException
+ {
+ }
+
+ /**
+ * Prepares a template for a call of the routine target.
+ *
+ * This method is never called without
+ * {@link #essentialChecks essentialChecks} having been called
+ * immediately prior and completing normally.
+ *
+ * The information available at this stage comes from the system
+ * catalogs, reflecting the static declaration of the target routine.
+ * The methods of target can be used to examine that
+ * catalog information; the {@link PLJavaBased PLJavaBased}
+ * memo holds additional derived information,
+ * including tuple descriptors for the inputs and outputs.
+ * (All routines, including those treated by PostgreSQL as
+ * returning a scalar result, are presented to a PL/Java handler with
+ * the inputs and outputs represented by {@link TupleTableSlot}.)
+ * The tuple descriptors seen at this stage may include attributes with
+ * polymorphic types, not resolvable to specific types until the
+ * {@code Template} instance this method returns is later applied at
+ * an actual call site.
+ *
+ * This method is never called for a target with
+ * {@link RegProcedure#returnsSet returnsSet()} true. If the language
+ * also implements {@link ReturningSets ReturningSets}, any such
+ * target will be passed to the
+ * {@link ReturningSets#prepareSRF prepareSRF} method instead;
+ * otherwise, it will incur an exception stating the language does not
+ * support returning sets.
+ *
+ * This method should return a {@link Template Template}, which may
+ * encapsulate any useful precomputed values based on the catalog
+ * information this method consulted.
+ *
+ * The template, when its {@link Template#specialize specialize} method
+ * is invoked on an actual {@link Lookup Lookup} instance, should return
+ * a {@link Routine Routine} able to apply the target function's logic
+ * when invoked any number of times on {@link Call Call} instances
+ * associated with the same {@code Lookup}.
+ *
+ * When there is no polymorphic or variadic-"any" funny business in
+ * target's declaration, this method may return a
+ * {@code Template} that ignores its argument and always returns the
+ * same {@code Routine}. It could even do so in all cases, if
+ * implementing a language where those dynamic details are left to user
+ * code.
+ */
+ Template prepare(RegProcedure> target, PLJavaBased memo)
+ throws SQLException;
+ }
+
+ /**
+ * To be implemented by a language that can be used to write functions
+ * returning sets (that is, more than a single result or row).
+ */
+ public interface ReturningSets extends PLJavaBasedLanguage
+ {
+ /**
+ * Performs the essential validation checks on a proposed
+ * PL/Java-based set-returning function.
+ *
+ * See {@link Routines#essentialChecks essentialChecks} for
+ * the explanation of what to consider 'essential' checks.
+ *
+ * This default implementation simply delegates to the
+ * {@link Routines#essentialChecks essentialChecks} method, which must
+ * therefore be prepared for subject to have either value of
+ * {@link RegProcedure#returnsSet returnsSet()}.
+ */
+ default void essentialSRFChecks(
+ RegProcedure> subject, PLJavaBased memo, boolean checkBody)
+ throws SQLException
+ {
+ /*
+ * A cast, because the alternative of having SetReturning extend
+ * Routines would allow Routines to be omitted from the implements
+ * clause of a language handler, which I would rather not encourage
+ * as a matter of style.
+ */
+ ((Routines)this).essentialChecks(subject, memo, checkBody);
+ }
+
+ /**
+ * Performs additional validation checks on a proposed
+ * PL/Java-based set-returning function.
+ *
+ * See {@link Routines#additionalChecks additionalChecks} for
+ * the explanation of what to consider 'additional' checks.
+ *
+ * This default implementation simply delegates to the
+ * {@link Routines#additionalChecks additionalChecks} method, which must
+ * therefore be prepared for subject to have either value of
+ * {@link RegProcedure#returnsSet returnsSet()}.
+ */
+ default void additionalSRFChecks(
+ RegProcedure> subject, PLJavaBased memo, boolean checkBody)
+ throws SQLException
+ {
+ ((Routines)this).additionalChecks(subject, memo, checkBody);
+ }
+
+ /**
+ * Prepares a template for a call of the set-returning function
+ * target.
+ *
+ * This method is never called without
+ * {@link #essentialSRFChecks essentialSRFChecks} having been called
+ * immediately prior and completing normally.
+ *
+ * This method is analogous to the
+ * {@link Routines#prepare prepare} method, but is called only for
+ * a target with {@link RegProcedure#returnsSet returnsSet()}
+ * true, and must return {@link SRFTemplate SRFTemplate} rather than
+ * {@link Template Template}.
+ *
+ * The documentation of the {@link Routines#prepare prepare} method
+ * further describes what is expected of an implementation.
+ */
+ SRFTemplate prepareSRF(RegProcedure> target, PLJavaBased memo)
+ throws SQLException;
+ }
+
+ /**
+ * To be implemented by a language that supports triggers.
+ *
+ * The methods of this interface will be called, instead of those declared
+ * in {@link Routines Routines}, for any function declared with return type
+ * {@link RegType#TRIGGER TRIGGER}. If a language does not implement
+ * this interface, any attempt to validate or use such a function will incur
+ * an exception.
+ */
+ public interface Triggers extends PLJavaBasedLanguage
+ {
+ /**
+ * Performs the essential validation checks on a proposed
+ * trigger function.
+ *
+ * See {@link Routines#essentialChecks Routines.essentialChecks} for
+ * details on what to check here.
+ *
+ * Any subject passed to this method is already known to be
+ * a function with no declared parameters and a non-set return type of
+ * {@link RegType#TRIGGER TRIGGER}.
+ *
+ * This default implementation checks nothing further.
+ */
+ default void essentialTriggerChecks(
+ RegProcedure
+ * See {@link Routines#additionalChecks Routines.additionalChecks} for
+ * details on what to check here.
+ *
+ * Any subject passed to this method is already known to be
+ * a function with no declared parameters and a non-set return type of
+ * {@link RegType#TRIGGER TRIGGER}, and to have passed the
+ * {@link #essentialTriggerChecks essentialTriggerChecks}.
+ *
+ * This default implementation checks nothing further.
+ */
+ default void additionalTriggerChecks(
+ RegProcedure
+ * This method is never called without
+ * {@link #essentialTriggerChecks essentialTriggerChecks} having
+ * been called immediately prior and completing normally.
+ *
+ * See {@link Routines#prepare Routines.prepare} for background on
+ * what to do here.
+ *
+ * This method should return a {@link TriggerTemplate TriggerTemplate},
+ * which may encapsulate any useful precomputed values based on
+ * the catalog information this method consulted.
+ *
+ * Any target passed to this method is already known to be
+ * a function with no declared parameters and a non-set return type of
+ * {@link RegType#TRIGGER TRIGGER}, and to have passed the
+ * {@link #essentialTriggerChecks essentialTriggerChecks}.
+ *
+ * The template, when its {@link TriggerTemplate#specialize specialize}
+ * method is invoked on a {@link Trigger Trigger} instance, should
+ * return a {@link TriggerFunction TriggerFunction} that can be invoked
+ * on a {@link TriggerData TriggerData} instance.
+ *
+ * The template may generate a {@code TriggerFunction} that encapsulates
+ * specifics of the {@code Trigger} such as its target table, name,
+ * arguments, enabled events, scope, and columns of interest.
+ * A {@code TriggerFunction} will not be invoked for any trigger except
+ * the one passed to the {@code specialize} call that returned it.
+ */
+ TriggerTemplate prepareTrigger(
+ RegProcedure
+ * In addition to implementing the abstract method declared here, a language
+ * that implements this interface takes up full responsibility for doing
+ * whatever must be done to give effect to any such transforms declared on
+ * routines that use the language. PostgreSQL itself provides nothing but
+ * a way to declare transforms and associate them with routine declarations.
+ *
+ * PL/Java will reject, at validation time when possible, any routine
+ * declared with {@code TRANSFORM FOR TYPE} if the language does not
+ * implement this interface.
+ *
+ * A language that does implement this interface can learn
+ * what transforms are to be applied by calling
+ * {@link PLJavaBased#transforms() memo.transforms()} in its
+ * {@link Routines#prepare prepare} and/or
+ * {@link Triggers#prepareTrigger prepareTrigger} methods, and perhaps
+ * also in its validation methods to detect configuration issues as early
+ * as possible.
+ */
+ public interface UsingTransforms extends PLJavaBasedLanguage
+ {
+ /**
+ * Performs validation checks on a {@link Transform} that purports to be
+ * usable with this language.
+ *
+ * PL/Java will already have checked that t's
+ * {@link Transform#language() language()} refers to this language.
+ * This method should use best effort to make sure that t's
+ * {@link Transform#fromSQL() fromSQL()} and
+ * {@link Transform#toSQL() toSQL()} functions are, in fact, functions
+ * that this language implementation can use to transform values between
+ * t's target PostgreSQL {@link Transform#type() type()} and
+ * a data type available to this language. See documentation of the
+ * {@link Transform#fromSQL() fromSQL()} and
+ * {@link Transform#toSQL() toSQL()} methods for more detail on what may
+ * need to be checked.
+ *
+ * It is possible for {@link Transform#fromSQL() fromSQL()}
+ * or {@link Transform#toSQL() toSQL()} to return
+ * a {@code RegProcedure} instance for which
+ * {@link RegProcedure#isValid() isValid()} is false, which indicates
+ * that this language's default from-SQL or to-SQL handling,
+ * respectively, is to be used for the transform's
+ * {@linkplain Transform#type() type}. In such cases, this method should
+ * check that this language has a usable default conversion in the
+ * indicated direction for that type.
+ *
+ * This method should return normally on success, otherwise throwing
+ * an informative exception. Unless there is a more-specific
+ * choice, {@link SQLSyntaxErrorException} with {@code SQLState}
+ * {@code 42P17} corresponds to PostgreSQL's
+ * {@code invalid_object_definition}.
+ */
+ void essentialTransformChecks(Transform t) throws SQLException;
+ }
+
+ /**
+ * To be implemented by a language that supports inline code blocks.
+ *
+ * A {@code DO} block is allowed to start, commit, and roll back top-level
+ * transactions, as long as it was not invoked inside an existing explicit
+ * transaction. The atomic parameter passed to
+ * {@link #execute execute} will be {@code true} if transaction control
+ * is disallowed.
+ *
+ * A handler may use this information to impose its own (for example,
+ * compile-time) limits on the availability of transaction-control
+ * operations. Any use of SPI by the code block will be appropriately
+ * limited with no need for attention from the handler, as PL/Java
+ * propagates the atomic/nonatomic flag to SPI always.
+ */
+ public interface InlineBlocks extends PLJavaBasedLanguage
+ {
+ /**
+ * Parses and executes an inline code block.
+ * @param source_text the inline code to be parsed and executed
+ * @param atomic true if transaction control actions must be disallowed
+ * within the code block
+ */
+ void execute(String source_text, boolean atomic) throws SQLException;
+ }
+
+ /**
+ * The result of a {@link Template#specialize specialize} call on
+ * a {@link Template Template}.
+ *
+ * An instance can incorporate whatever can be precomputed based on the
+ * resolved parameter types and other information available to
+ * {@code specialize}. Its {@link #call call} method will then be invoked to
+ * supply the arguments and produce the results for each call made
+ * at that call site.
+ */
+ @FunctionalInterface
+ public interface Routine
+ {
+ /**
+ * Actually executes the prepared and specialized {@code Routine}, using
+ * the arguments and other call-specific information passed in
+ * {@code fcinfo}.
+ *
+ * Various special cases of routine calls (triggers, procedure calls,
+ * and so on) can be distinguished by the specific subtypes of
+ * {@link Call.Context} that may be returned by
+ * {@code fcinfo.}{@link Call#context() context()}.
+ */
+ void call(Call fcinfo) throws SQLException;
+ }
+
+ /**
+ * The result of a {@link TriggerTemplate#specialize specialize} call on
+ * a {@link TriggerTemplate TriggerTemplate}.
+ *
+ * An instance can incorporate whatever can be precomputed based on the
+ * specific {@link Trigger Trigger} that was passed to {@code specialize}.
+ * Its {@link #apply apply} method will then be invoked to act on
+ * the {@link TriggerData TriggerData} and produce the results each time
+ * that trigger fires.
+ */
+ @FunctionalInterface
+ public interface TriggerFunction
+ {
+ /**
+ * Actually executes the prepared and specialized
+ * {@code TriggerFunction}, with the triggering data available in
+ * triggerData.
+ *
+ * The return value, ignored for an {@link Called#AFTER AFTER} trigger,
+ * and restricted to null for any
+ * {@link Called#BEFORE BEFORE} {@link Scope#STATEMENT STATEMENT}
+ * trigger, can influence the triggering operation for other types
+ * of triggers. To permit the operation with no changes by the trigger,
+ * return exactly {@link TriggerData#triggerTuple triggerTuple} (for
+ * a trigger on {@link Event#INSERT INSERT} or
+ * {@link Event#DELETE DELETE}), or exactly
+ * {@link TriggerData#newTuple newTuple} (for a trigger on
+ * {@link Event#UPDATE UPDATE}). To suppress the triggering operation,
+ * return null.
+ * @return a TupleTableSlot, or null
+ */
+ TupleTableSlot apply(TriggerData triggerData) throws SQLException;
+ }
+
+ /**
+ * The result of a {@link Routines#prepare prepare} call on a PL/Java-based
+ * routine.
+ *
+ * An instance should depend only on the static catalog information for the
+ * routine as passed to {@code prepare}, and may encapsulate any values that
+ * can be precomputed from that information alone. Its
+ * {@link #specialize specialize} method will be called, passing information
+ * specific to a call site, to obtain a {@link Routine Routine}.
+ */
+ @FunctionalInterface
+ public interface Template
+ {
+ /**
+ * Given the information present at a particular call site, specialize
+ * this template into a {@link Routine Routine} that will handle calls
+ * through this call site.
+ *
+ * Typical activities for {@code specialize} would be to consult
+ * flinfo's {@link Lookup#inputsDescriptor inputsDescriptor}
+ * and {@link Lookup#outputsDescriptor outputsDescriptor} for the number
+ * and types of the expected input and output parameters, though it is
+ * unnecessary if the tuple descriptors obtained at
+ * {@link Routines#prepare prepare} time included no unresolved types.
+ * The {@link Lookup#inputsAreSpread inputsAreSpread} method should be
+ * consulted if the routine has a variadic parameter of the wildcard
+ * {@code "any"} type.
+ */
+ Routine specialize(Lookup flinfo) throws SQLException;
+ }
+
+ /**
+ * Superinterface for the result of a
+ * {@link ReturningSets#prepareSRF prepareSRF} call on a PL/Java-based
+ * set-returning function.
+ *
+ * An instance returned by {@link ReturningSets#prepareSRF prepareSRF} must
+ * implement at least one of the member subinterfaces. If it implements
+ * more than one, it will need to override the {@link #negotiate negotiate}
+ * method to select the behavior to be used at a given call site.
+ */
+ public interface SRFTemplate
+ {
+ /**
+ * Returns the index of a preferred subinterface of {@code SRFTemplate}
+ * among a list of those the caller supports.
+ *
+ * The list is ordered with a caller's more-preferred choices early.
+ *
+ * An implementation could simply return the first index of an
+ * allowed class C such that
+ * {@code this instanceof C} to use the caller's preferred method
+ * always, or could make a choice informed by characteristics of
+ * the template.
+ * @return the index within allowed of the interface to be
+ * used at this call site, or -1 if no interface in allowed
+ * is supported.
+ */
+ int negotiate(List
+ * This default implementation simply returns
+ * {@code allowed.indexOf(Materialize.class)}.
+ */
+ @Override
+ default int negotiate(List
+ * This default implementation simply returns
+ * {@code allowed.indexOf(ValuePerCall.class)}.
+ */
+ @Override
+ default int negotiate(List
+ * An instance can incorporate whatever can be precomputed based on the
+ * resolved parameter types and other information available to
+ * {@code specializeValuePerCall}. Its {@link #firstCall firstCall} method
+ * will then be invoked, for each call made at that call site, to supply the
+ * arguments and obtain an instance of {@link SRFNext SRFNext} whose
+ * {@link SRFNext#nextResult nextResult} method will be called, as many
+ * times as needed, to retrieve all rows of the result.
+ */
+ @FunctionalInterface
+ public interface SRFFirst
+ {
+ /**
+ * Executes the prepared and specialized {@code SRFFirst} code, using
+ * the arguments and other call-specific information passed in
+ * {@code fcinfo} and returns an instance of {@link SRFNext SRFNext}
+ * to produce a result set row by row.
+ *
+ * This method should not access fcinfo's
+ * {@link RegProcedure.Call#result result} or
+ * {@link RegProcedure.Call#isNull isNull} methods to return any value,
+ * but should return an instance of {@code SRFNext} that will do so.
+ */
+ SRFNext firstCall(Call fcinfo) throws SQLException;
+ }
+
+ /**
+ * The result of a {@link SRFFirst#firstCall firstCall} call on an instance
+ * of {@link SRFFirst SRFFirst}.
+ *
+ * The {@link #nextResult nextResult} method will be called repeatedly
+ * as long as its return value indicates another row may follow, unless
+ * PostgreSQL earlier determines no more rows are needed.
+ *
+ * The {@link #close close} method will be called after the last call of
+ * {@code nextResult}, whether because all rows have been read or because
+ * PostgreSQL has read all it needs. It is not called, however,
+ * if {@code nextResult} has returned {@link Result#SINGLE Result.SINGLE}.
+ *
+ */
+ public interface SRFNext extends AutoCloseable
+ {
+ /**
+ * Called when PostgreSQL will be making no further calls of
+ * {@link #nextResult nextResult} for this result set, which may be
+ * before all results have been fetched.
+ *
+ * When a degenerate single-row set is returned (as indicated by
+ * {@link #nextResult nextResult} returning
+ * {@link Result#SINGLE Result.SINGLE}), this method is not called.
+ */
+ void close();
+
+ /**
+ * Called to return a single result.
+ *
+ * As with non-set-returning routines, this method should store result
+ * values into {@link RegProcedure.Call#result fcinfo.result()} or set
+ * {@link RegProcedure.Call#isNull fcinfo.isNull(true)} (which, in this
+ * context, produces a row of all nulls). If there is no result
+ * to store, the method should return {@link Result#END Result.END}:
+ * no row will be produced, and the result set is considered complete.
+ *
+ * If the method has exactly one row to return, it may store the values
+ * and return {@link Result#SINGLE Result.SINGLE}: the result will be
+ * considered to be just that one row. None of the rest of the
+ * set-returning protocol will be involved, and
+ * {@link SRFNext#close close()} will not be called.
+ *
+ * Otherwise, the method should return
+ * {@link Result#MULTIPLE Result.MULTIPLE} after storing each row, and
+ * conclude by returning {@link Result#END Result.END} from the final
+ * call (without storing anything).
+ *
+ * It is a protocol violation to return
+ * {@link Result#SINGLE Result.SINGLE} from any but the very first call.
+ *
+ * The arguments in
+ * {@link RegProcedure.Call#arguments fcinfo.arguments()} will not be
+ * changing as the rows of a single result are retrieved. Any argument
+ * values that will be referred to repeatedly may be worth fetching once
+ * in the {@link SRFFirst#firstCall firstCall} method and their Java
+ * representations captured in this object, rather than fetching and
+ * converting them repeatedly.
+ */
+ Result nextResult(Call fcinfo) throws SQLException;
+
+ /**
+ * Used to indicate the state of the result sequence on return from
+ * a single call in the {@code ValuePerCall} protocol.
+ */
+ enum Result
+ {
+ /**
+ * There is exactly one row and this call has returned it.
+ *
+ * None of the rest of the set-returning protocol will be involved,
+ * and {@link SRFNext#close close()} will not be called.
+ */
+ SINGLE,
+
+ /**
+ * This call has returned one of possibly multiple rows, and
+ * another call should be made to retrieve the next row if any.
+ */
+ MULTIPLE,
+
+ /**
+ * This call has no row to return and the result sequence
+ * is complete.
+ */
+ END
+ }
+ }
+
+ /**
+ * The result of a {@link Triggers#prepareTrigger prepareTrigger} call on
+ * a PL/Java-based trigger function.
+ *
+ * An instance should depend only on the static catalog information for the
+ * function as passed to {@code prepareTrigger}, and may encapsulate any
+ * values that can be precomputed from that information alone. Its
+ * {@link #specialize specialize} method will be called, passing information
+ * specific to one trigger, to obtain a
+ * {@link TriggerFunction TriggerFunction}.
+ */
+ @FunctionalInterface
+ public interface TriggerTemplate
+ {
+ /**
+ * Given the specifics of one {@link Trigger Trigger}, specialize
+ * this template into a {@link TriggerFunction TriggerFunction} that
+ * will handle calls through this trigger.
+ *
+ * Typical activities for {@code specialize} would be to consult
+ * trigger's {@link Trigger#name name},
+ * {@link Trigger#relation relation}, {@link Trigger#called called},
+ * {@link Trigger#events events}, {@link Trigger#scope scope},
+ * {@link Trigger#arguments arguments}, and
+ * {@link Trigger#columns columns} to
+ * determine the kind of trigger it is, and fold those values into
+ * the returned {@code TriggerFunction}.
+ *
+ * This stage is well suited for checking that the characteristics of
+ * the trigger (events, scope, when called, arguments, column types of
+ * the target table) conform to what the trigger function can handle.
+ */
+ TriggerFunction specialize(Trigger trigger) throws SQLException;
+ }
+}
diff --git a/pljava-api/src/main/java/org/postgresql/pljava/RolePrincipal.java b/pljava-api/src/main/java/org/postgresql/pljava/RolePrincipal.java
new file mode 100644
index 000000000..cd68813de
--- /dev/null
+++ b/pljava-api/src/main/java/org/postgresql/pljava/RolePrincipal.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2022 Tada AB and other contributors, as listed below.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the The BSD 3-Clause License
+ * which accompanies this distribution, and is available at
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Contributors:
+ * Chapman Flack
+ */
+package org.postgresql.pljava;
+
+import java.io.InvalidObjectException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+import java.nio.file.attribute.GroupPrincipal;
+
+import java.util.function.Function;
+
+import org.postgresql.pljava.sqlgen.Lexicals.Identifier;
+import org.postgresql.pljava.sqlgen.Lexicals.Identifier.Pseudo;
+import org.postgresql.pljava.sqlgen.Lexicals.Identifier.Simple;
+
+public abstract class RolePrincipal extends BasePrincipal
+{
+ private static final long serialVersionUID = 5650953533699613976L;
+
+ RolePrincipal(String name)
+ {
+ super(name);
+ constrain(IllegalArgumentException::new);
+ }
+
+ RolePrincipal(Simple name)
+ {
+ super(name);
+ constrain(IllegalArgumentException::new);
+ /*
+ * Ensure the subclasses' PUBLIC singletons really are, by rejecting the
+ * Pseudo.PUBLIC identifier in this constructor. The subclasses use
+ * private constructors that call the specialized one below when
+ * initializing their singletons.
+ */
+ if ( s_public == name )
+ throw new IllegalArgumentException(
+ "attempt to create non-singleton PUBLIC RolePrincipal");
+ }
+
+ RolePrincipal(Pseudo name)
+ {
+ super(name);
+ constrain(IllegalArgumentException::new);
+ }
+
+ private final
+ * Each concrete subclass of {@code RolePrincipal} has a singleton
+ * {@code PUBLIC} instance, which will only compare equal to itself (this
+ * method is not the place to say everything matches {@code PUBLIC}, because
+ * {@code equals} should be symmetric, and security checks should not be).
+ * Otherwise, the result is that of
+ * {@link Identifier#equals(Object) Identifier.equals}.
+ *
+ * Note that these {@code PUBLIC} instances are distinct from the wild-card
+ * principal names that can appear in the Java policy file: those are
+ * handled without ever instantiating the class, and simply match any
+ * principal with the identically-spelled class name.
+ */
+ @Override
+ public final boolean equals(Object other)
+ {
+ if ( this == other )
+ return true;
+ /*
+ * Because the pseudo "PUBLIC" instances are restricted to being
+ * singletons (one per RolePrincipal subclass), the above test will have
+ * already handled the matching case for those. Below, if either one is
+ * a PUBLIC instance, its m_name won't match anything else, which is ok
+ * because of the PostgreSQL rule that no role can have a potentially
+ * matching name anyway.
+ */
+ if ( ! getClass().isInstance(other) )
+ return false;
+ RolePrincipal o = (RolePrincipal)other;
+ return m_name.equals(o.m_name);
+ }
+
+ public static final class Authenticated extends RolePrincipal
+ {
+ private static final long serialVersionUID = -4558155344619605758L;
+
+ public static final Authenticated PUBLIC = new Authenticated(s_public);
+
+ public Authenticated(String name)
+ {
+ super(name);
+ }
+
+ public Authenticated(Simple name)
+ {
+ super(name);
+ }
+
+ private Authenticated(Pseudo name)
+ {
+ super(name);
+ }
+
+ private Object readResolve() throws ObjectStreamException
+ {
+ return m_name == s_public ? PUBLIC : this;
+ }
+ }
+
+ public static final class Session extends RolePrincipal
+ {
+ private static final long serialVersionUID = -598305505864518470L;
+
+ public static final Session PUBLIC = new Session(s_public);
+
+ public Session(String name)
+ {
+ super(name);
+ }
+
+ public Session(Simple name)
+ {
+ super(name);
+ }
+
+ private Session(Pseudo name)
+ {
+ super(name);
+ }
+
+ private Object readResolve() throws ObjectStreamException
+ {
+ return m_name == s_public ? PUBLIC : this;
+ }
+ }
+
+ public static final class Outer extends RolePrincipal
+ {
+ private static final long serialVersionUID = 2177159367185354785L;
+
+ public static final Outer PUBLIC = new Outer(s_public);
+
+ public Outer(String name)
+ {
+ super(name);
+ }
+
+ public Outer(Simple name)
+ {
+ super(name);
+ }
+
+ private Outer(Pseudo name)
+ {
+ super(name);
+ }
+
+ private Object readResolve() throws ObjectStreamException
+ {
+ return m_name == s_public ? PUBLIC : this;
+ }
+ }
+
+ public static final class Current extends RolePrincipal
+ implements GroupPrincipal
+ {
+ private static final long serialVersionUID = 2816051825662188997L;
+
+ public static final Current PUBLIC = new Current(s_public);
+
+ public Current(String name)
+ {
+ super(name);
+ }
+
+ public Current(Simple name)
+ {
+ super(name);
+ }
+
+ private Current(Pseudo name)
+ {
+ super(name);
+ }
+
+ private Object readResolve() throws ObjectStreamException
+ {
+ return m_name == s_public ? PUBLIC : this;
+ }
+ }
+}
diff --git a/pljava-api/src/main/java/org/postgresql/pljava/TargetList.java b/pljava-api/src/main/java/org/postgresql/pljava/TargetList.java
new file mode 100644
index 000000000..5e643a2af
--- /dev/null
+++ b/pljava-api/src/main/java/org/postgresql/pljava/TargetList.java
@@ -0,0 +1,971 @@
+/*
+ * Copyright (c) 2023-2025 Tada AB and other contributors, as listed below.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the The BSD 3-Clause License
+ * which accompanies this distribution, and is available at
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Contributors:
+ * Chapman Flack
+ */
+package org.postgresql.pljava;
+
+import java.sql.SQLException; // for javadoc
+import java.sql.SQLXML; // for javadoc
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Iterator;
+import java.util.List;
+
+import java.util.stream.Stream;
+
+import org.postgresql.pljava.Adapter.As;
+import org.postgresql.pljava.Adapter.AsBoolean;
+import org.postgresql.pljava.Adapter.AsByte;
+import org.postgresql.pljava.Adapter.AsChar;
+import org.postgresql.pljava.Adapter.AsDouble;
+import org.postgresql.pljava.Adapter.AsFloat;
+import org.postgresql.pljava.Adapter.AsInt;
+import org.postgresql.pljava.Adapter.AsLong;
+import org.postgresql.pljava.Adapter.AsShort;
+
+import org.postgresql.pljava.model.Attribute;
+import org.postgresql.pljava.model.Portal; // for javadoc
+import org.postgresql.pljava.model.TupleDescriptor; // for javadoc
+import org.postgresql.pljava.model.TupleTableSlot;
+
+import org.postgresql.pljava.sqlgen.Lexicals.Identifier.Simple;
+
+/**
+ * Identifies attributes to be retrieved from a set of tuples.
+ *
+ * {@code TargetList} is more general than {@link Projection Projection}: in a
+ * {@code Projection}, no attribute can appear more than once, but repetition
+ * is possible in a {@code TargetList}.
+ *
+ * In general, it will be more efficient, if processing logic requires more than
+ * one copy of some attribute's value, to simply mention the attribute once in a
+ * {@code Projection}, and have the Java logic then copy the value, rather than
+ * fetching and converting it twice from the database native form. But there
+ * may be cases where that isn't workable, such as when the value is needed in
+ * different Java representations from different {@link Adapter}s, or when the
+ * Java representation is a type like {@link SQLXML} that can only be used once.
+ * Such cases call for a {@code TargetList} in which the attribute is mentioned
+ * more than once, to be separately fetched.
+ *
+ * Given a {@code TargetList}, query results can be processed by supplying a
+ * lambda body to {@link #applyOver(Iterable,Cursor.Function) applyOver}. The
+ * lambda will be supplied a {@link Cursor Cursor} whose {@code apply} methods
+ * can be used to break out the wanted values on each row, in the
+ * {@code TargetList} order.
+ */
+public interface TargetList extends List
+ * The prime example of a {@code Projection} is a {@link TupleDescriptor} as
+ * obtained, for example, from the {@link Portal} for a query result.
+ *
+ * To preserve the "no attribute appears more than once" property, the only
+ * new {@code Projection}s derivable from an existing one involve selecting
+ * a subset of its attributes, and possibly changing their order. The
+ * {@code project} methods taking attribute names, attribute indices, or the
+ * attributes themselves can be used to do so, as can the {@code subList}
+ * method.
+ */
+ interface Projection extends TargetList
+ {
+ /**
+ * From this {@code Projection}, returns a {@code Projection} containing
+ * only the attributes matching the supplied names and in the
+ * order of the argument list.
+ * @throws IllegalArgumentException if more names are supplied than this
+ * Projection has attributes, or if any remain unmatched after matching
+ * each attribute in this Projection at most once.
+ */
+ Projection project(Simple... names);
+
+ /**
+ * From this {@code Projection}, returns a {@code Projection} containing
+ * only the attributes matching the supplied names and in the
+ * order of the argument list.
+ *
+ * The names will be converted to {@link Simple Identifier.Simple} by
+ * its {@link Simple#fromJava fromJava} method before comparison.
+ * @throws IllegalArgumentException if more names are supplied than this
+ * Projection has attributes, or if any remain unmatched after matching
+ * each attribute in this Projection at most once.
+ */
+ default Projection project(CharSequence... names)
+ {
+ return project(
+ Arrays.stream(names)
+ .map(CharSequence::toString)
+ .map(Simple::fromJava)
+ .toArray(Simple[]::new)
+ );
+ }
+
+ /**
+ * Returns a {@code Projection} containing only the attributes found
+ * at the supplied indices in this {@code Projection}, and in
+ * the order of the argument list.
+ *
+ * The index of the first attribute is zero.
+ * @throws IllegalArgumentException if more indices are supplied than
+ * this Projection has attributes, if any index is negative or beyond
+ * the last index in this Projection, or if any index appears more than
+ * once.
+ */
+ Projection project(int... indices);
+
+ /**
+ * Returns a {@code Projection} containing only the attributes found
+ * at the supplied indices in this {@code Projection}, and in
+ * the order of the argument list.
+ *
+ * The index of the first attribute is zero.
+ * @throws IllegalArgumentException if more indices are supplied than
+ * this Projection has attributes, if any index is negative or beyond
+ * the last index in this Projection, or if any index appears more than
+ * once.
+ */
+ Projection project(short... indices);
+
+ /**
+ * Returns a {@code Projection} containing only the attributes whose
+ * indices in this {@code Projection} are set (true) in the supplied
+ * {@link BitSet BitSet}, and in the same order.
+ *
+ * The index of the first attribute is zero.
+ * @throws IllegalArgumentException if more bits are set than this
+ * Projection has attributes, or if any bit is set beyond the last index
+ * in this Projection.
+ */
+ Projection project(BitSet indices);
+
+ /**
+ * Returns a {@code Projection} containing only the attributes whose
+ * indices in this {@code Projection} are set (true) in the supplied
+ * {@link BitSet BitSet}, and in the same order.
+ *
+ * The index of the first attribute is one.
+ * @throws IllegalArgumentException if more bits are set than this
+ * Projection has attributes, or if any bit is set before the first or
+ * beyond the last corresponding to a 1-based index in this Projection.
+ */
+ Projection sqlProject(BitSet indices);
+
+ /**
+ * Like {@link #project(int...) project(int...)} but using SQL's 1-based
+ * indexing convention.
+ *
+ * The index of the first attribute is 1.
+ * @throws IllegalArgumentException if more indices are supplied than
+ * this Projection has attributes, if any index is nonpositive or beyond
+ * the last 1-based index in this Projection, or if any index appears
+ * more than once.
+ */
+ Projection sqlProject(int... indices);
+
+ /**
+ * Like {@link #project(int...) project(int...)} but using SQL's 1-based
+ * indexing convention.
+ *
+ * The index of the first attribute is 1.
+ * @throws IllegalArgumentException if more indices are supplied than
+ * this Projection has attributes, if any index is nonpositive or beyond
+ * the last 1-based index in this Projection, or if any index appears
+ * more than once.
+ */
+ Projection sqlProject(short... indices);
+
+ /**
+ * Returns a {@code Projection} containing only attributes
+ * and in the order of the argument list.
+ *
+ * The attributes must be found in this {@code Projection} by exact
+ * reference identity.
+ * @throws IllegalArgumentException if more attributes are supplied than
+ * this Projection has, or if any remain unmatched after matching
+ * each attribute in this Projection at most once.
+ */
+ Projection project(Attribute... attributes);
+
+ @Override
+ Projection subList(int fromIndex, int toIndex);
+ }
+
+ @Override
+ TargetList subList(int fromIndex, int toIndex);
+
+ /**
+ * Like {@link #get(int) get} but following the SQL convention where the
+ * first element has index 1.
+ */
+ default Attribute sqlGet(int oneBasedIndex)
+ {
+ try
+ {
+ return get(oneBasedIndex - 1);
+ }
+ catch ( IndexOutOfBoundsException e )
+ {
+ throw (IndexOutOfBoundsException)
+ new IndexOutOfBoundsException(String.format(
+ "sqlGet() one-based index %d should be > 0 and <= %d",
+ oneBasedIndex, size()
+ ))
+ .initCause(e);
+ }
+ }
+
+ /**
+ * Executes the function f, once, supplying a
+ * {@link Cursor Cursor} that can be iterated over the supplied
+ * tuples and used to process each tuple.
+ * @return whatever f returns.
+ */
+
+ * The {@code Cursor} can be iterated, just as if a one-row
+ * {@code Iterable
+ * Being derived from a {@link TargetList}, a {@code Cursor} serves directly
+ * as an {@code Iterator
+ * Being bound to a source of tuples, a {@code Cursor} also implements
+ * {@code Iterable}, and can supply an iterator over the bound tuples in
+ * order. The {@code Cursor} is mutated during the iteration, having a
+ * current row that becomes each tuple in turn. The object returned by that
+ * iterator is the {@code Cursor} itself, so the caller has no need for the
+ * iteration variable, and can use the "unnamed variable" {@code _} for it,
+ * in Java versions including that feature (which appears in Java 21 but
+ * only with {@code --enable-preview}). In older Java versions it can be
+ * given some other obviously throwaway name.
+ *
+ * When a {@code Cursor} has a current row, its {@code apply} methods can be
+ * used to execute a lambda body with its parameters mapped to the row's
+ * values, in {@code TargetList} order, or to a prefix of those, should
+ * a lambda with fewer parameters be supplied.
+ *
+ * Each overload of {@code apply} takes some number of
+ * {@link Adapter Adapter} instances, each of which must be suited to the
+ * PostgreSQL type at its corresponding position, followed by a lambda body
+ * with the same number of parameters, each of which will receive the value
+ * from the corresponding {@code Adapter}, and have an inferred type
+ * matching what that {@code Adapter} produces.
+ *
+ * Within a lambda body with fewer parameters than the length of the
+ * {@code TargetList}, the {@code Cursor}'s attribute iterator has been
+ * advanced by the number of columns consumed. It can be used again to apply
+ * an inner lambda body to remaining columns. This "curried" style can be
+ * useful when the number or types of values to be processed will not
+ * directly fit any available {@code apply} signature.
+ *
+ * As the {@code apply} overloads for reference-typed values and those for
+ * primitive values are separate, currying must be used when processing a
+ * mix of reference and primitive types.
+ *
+ * The {@code Cursor}'s attribute iterator is reset each time the tuple
+ * iterator moves to a new tuple. It is also reset on return (normal or
+ * exceptional) from an outermost {@code apply}, in case another function
+ * should then be applied to the row.
+ *
+ * The attribute iterator is not reset on return from an inner (curried)
+ * {@code apply}. Therefore, it is possible to process a tuple having
+ * repeating groups of attributes with matching types, reusing an inner
+ * lambda and its matching adapters for each occurrence of the group.
+ *
+ * If the tuple is nothing but repeating groups, the effect can still be
+ * achieved by using the zero-parameter {@code apply} overload as the
+ * outermost.
+ */
+ interface Cursor extends Iterator
+ * Because the {@code Iterator} will produce the same {@code Cursor}
+ * instance on each iteration, and the instance is mutated, saving
+ * values the iterator returns will not have effects one might expect,
+ * and no more than one iteration should be in progress at a time.
+ *
+ * The {@code Iterator
+ * The stream should be used within the scope of the
+ * {@link #applyOver(Iterable,Function) applyOver} that has made
+ * this {@code Cursor} available.
+ *
+ * Because the {@code Stream} will produce the same {@code Cursor}
+ * instance repeatedly, and the instance is mutated, saving instances
+ * will not have effects one might expect, and no more than one
+ * stream should be in progress at a time. Stateful operations such as
+ * {@code distinct} or {@code sorted} will make no sense applied to
+ * these instances. Naturally, this method does not return a parallel
+ * {@code Stream}.
+ *
+ * These restrictions do not satisfy all expectations of a
+ * {@code Stream}, and may be topics for future work as this API is
+ * refined.
+ *
+ * The {@code IteratorThe design of the ResourceOwner API is modeled on our MemoryContext API,
+ * which has proven very flexible and successful ... It is tempting to consider
+ * unifying ResourceOwners and MemoryContexts into a single object type, but
+ * their usage patterns are sufficiently different ...."
+ *unifying ResourceOwners and MemoryContexts into a single
+ * object type
, PL/Java here makes them both available as subtypes of a
+ * common interface, so either can be chosen to place an appropriate temporal
+ * scope on a PL/Java object.
+ */
+public interface Lifespan
+{
+}
diff --git a/pljava-api/src/main/java/org/postgresql/pljava/PLJavaBasedLanguage.java b/pljava-api/src/main/java/org/postgresql/pljava/PLJavaBasedLanguage.java
new file mode 100644
index 000000000..9cc0cd218
--- /dev/null
+++ b/pljava-api/src/main/java/org/postgresql/pljava/PLJavaBasedLanguage.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) 2023-2025 Tada AB and other contributors, as listed below.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the The BSD 3-Clause License
+ * which accompanies this distribution, and is available at
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Contributors:
+ * Chapman Flack
+ */
+package org.postgresql.pljava;
+
+import java.sql.SQLException;
+import java.sql.SQLSyntaxErrorException;
+
+import java.util.List;
+
+import org.postgresql.pljava.model.ProceduralLanguage; // javadoc
+import org.postgresql.pljava.model.ProceduralLanguage.PLJavaBased;
+import org.postgresql.pljava.model.RegProcedure;
+import org.postgresql.pljava.model.RegProcedure.Call;
+import org.postgresql.pljava.model.RegProcedure.Call.Context.TriggerData; // jvd
+import org.postgresql.pljava.model.RegProcedure.Lookup;
+import org.postgresql.pljava.model.RegType;
+import org.postgresql.pljava.model.Transform;
+import org.postgresql.pljava.model.Trigger;
+import org.postgresql.pljava.model.Trigger.ForTrigger;
+import org.postgresql.pljava.model.TupleTableSlot; // javadoc
+
+import org.postgresql.pljava.annotation.Trigger.Called; // javadoc
+import org.postgresql.pljava.annotation.Trigger.Event; // javadoc
+import org.postgresql.pljava.annotation.Trigger.Scope; // javadoc
+
+/**
+ * Interface for a procedural language on PL/Java infrastructure.
+ *Transaction control
+ *Transaction control
+ *
+ * overall_result = targetlist.applyOver(tuples, c ->
+ * {
+ * var resultCollector = ...;
+ * for ( Cursor _ : c )
+ * {
+ * var oneResult = c.apply(
+ * adap0, adap1,
+ * ( val0, val1 ) -> c.apply(
+ * adap2, adap3,
+ * ( val2, val3 ) -> process(val0, val1, val2, val3)));
+ * resultCollector.collect(oneResult);
+ * }
+ * return resultCollector;
+ * });
+ *
+ *