diff --git a/.github/workflows/ci-runnerpg.yml b/.github/workflows/ci-runnerpg.yml
index c534680a1..ab261b3f1 100644
--- a/.github/workflows/ci-runnerpg.yml
+++ b/.github/workflows/ci-runnerpg.yml
@@ -7,9 +7,9 @@ name: PL/Java CI with PostgreSQL version supplied by the runner
on:
push:
- branches: [ master, REL1_6_STABLE ]
+ branches: [ master, REL1_7_STABLE, REL1_6_STABLE ]
pull_request:
- branches: [ master, REL1_6_STABLE ]
+ branches: [ master, REL1_7_STABLE, REL1_6_STABLE ]
jobs:
build:
diff --git a/appveyor.yml b/appveyor.yml
index a0e534ee2..5402ddbb0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -14,55 +14,55 @@ environment:
matrix:
# - SYS: MINGW
# JDK: 9
-# PG: 12
+# PG: 14
# - SYS: MINGW
# JDK: 10
-# PG: 12
+# PG: 14
- SYS: MINGW
JDK: 11
- PG: 12
+ PG: 14
- SYS: MINGW
JDK: 12
- PG: 12
+ PG: 14
- SYS: MINGW
JDK: 13
- PG: 12
+ PG: 14
- SYS: MINGW
JDK: 14
- PG: 12
+ PG: 14
- SYS: MINGW
JDK: 15
- PG: 12
+ PG: 14
- SYS: MSVC
JDK: 15
- PG: 12
- - SYS: MSVC
- JDK: 14
- PG: 12
- - SYS: MSVC
- JDK: 13
- PG: 12
- - SYS: MSVC
- JDK: 12
- PG: 12
+ PG: 13
+# - SYS: MSVC
+# JDK: 14
+# PG: 13
+# - SYS: MSVC
+# JDK: 13
+# PG: 13
+# - SYS: MSVC
+# JDK: 12
+# PG: 13
- SYS: MSVC
JDK: 11
- PG: 12
+ PG: 13
# - SYS: MSVC
# JDK: 10
-# PG: 12
+# PG: 13
# - SYS: MSVC
# JDK: 9
# PG: 12
- - SYS: MSVC
- JDK: 14
- PG: 11
- - SYS: MSVC
- JDK: 14
- PG: 10
- - SYS: MSVC
- JDK: 14
- PG: 9.6
+# - SYS: MSVC
+# JDK: 14
+# PG: 11
+# - SYS: MSVC
+# JDK: 14
+# PG: 10
+# - SYS: MSVC
+# JDK: 14
+# PG: 9.6
before_build:
- ps: .appveyor/appveyor_download_java.ps1
- set JAVA_HOME=%ProgramFiles%\Java\jdk%JDK%
diff --git a/pljava-ant/pom.xml b/pljava-ant/pom.xml
index 15e6c6751..f28fa7ee3 100644
--- a/pljava-ant/pom.xml
+++ b/pljava-ant/pom.xml
@@ -4,7 +4,7 @@
+ * 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()
+ {
+ AccessController.checkPermission(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
+ * 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
+ * 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
+ * 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..3c1619caa
--- /dev/null
+++ b/pljava-api/src/main/java/org/postgresql/pljava/TargetList.java
@@ -0,0 +1,921 @@
+/*
+ * Copyright (c) 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
+ * 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.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);
+
+ /**
+ * 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);
+
+ /**
+ * 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 Iterator af,
+ L16The 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/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
+ * 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;
+ * });
+ *
+ *