diff --git a/.travis.yml b/.travis.yml index ab9964768..c6403a367 100644 --- a/.travis.yml +++ b/.travis.yml @@ -250,6 +250,22 @@ script: | // state 9: must be end of input (o,p,q) -> null == o ); + + /* + * Also confirm that the generated undeploy actions work. + */ + succeeding &= stateMachine( + "remove jar void result", + null, + + q(c, "SELECT sqlj.remove_jar('examples', true)") + .flatMap(Node::semiFlattenDiagnostics) + .peek(Node::peek), + + (o,p,q) -> isDiagnostic(o, Set.of("error")) ? 1 : -2, + (o,p,q) -> isVoidResultSet(o, 1, 1) ? 3 : false, + (o,p,q) -> null == o + ); } } catch ( Throwable t ) { diff --git a/appveyor.yml b/appveyor.yml index 8fab11664..ca13103dd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -188,6 +188,22 @@ test_script: // state 9: must be end of input (o,p,q) -> null == o ); + + /* + * Also confirm that the generated undeploy actions work. + */ + succeeding &= stateMachine( + "remove jar void result", + null, + + q(c, "SELECT sqlj.remove_jar('examples', true)") + .flatMap(Node::semiFlattenDiagnostics) + .peek(Node::peek), + + (o,p,q) -> isDiagnostic(o, Set.of("error")) ? 1 : -2, + (o,p,q) -> isVoidResultSet(o, 1, 1) ? 3 : false, + (o,p,q) -> null == o + ); } } catch ( Throwable t ) { diff --git a/pljava-api/src/main/java/org/postgresql/pljava/annotation/Cast.java b/pljava-api/src/main/java/org/postgresql/pljava/annotation/Cast.java new file mode 100644 index 000000000..140993f85 --- /dev/null +++ b/pljava-api/src/main/java/org/postgresql/pljava/annotation/Cast.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a PostgreSQL {@code CAST}. + *

+ * May annotate a Java method (which should also carry a + * {@link Function @Function} annotation, making it a PostgreSQL function), + * or a class or interface (just to have a place to put it when not directly + * associated with a method). + *

+ * If not applied to a method, must supply {@code path=} specifying + * {@code BINARY} or {@code INOUT}. + *

+ * The source and target types must be specified with {@code from} and + * {@code to}, unless the annotation appears on a method, in which case these + * default to the first parameter and return types of the function, + * respectively. + *

+ * The cast will, by default, have to be applied explicitly, unless + * {@code application=ASSIGNMENT} or {@code application=IMPLICIT} is given. + * + * @author Chapman Flack + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Repeatable(Cast.Container.class) +@Retention(RetentionPolicy.CLASS) +public @interface Cast +{ + /** + * When this cast can be applied: only in explicit form, when used in + * assignment context, or implicitly whenever needed. + */ + enum Application { EXPLICIT, ASSIGNMENT, IMPLICIT }; + + /** + * A known conversion path when a dedicated function is not supplied: + * {@code BINARY} for two types that are known to have the same internal + * representation, or {@code INOUT} to invoke the first type's text-output + * function followed by the second type's text-input function. + */ + enum Path { BINARY, INOUT }; + + /** + * The source type to be cast. Will default to the first parameter type of + * the associated function, if known. + *

+ * PostgreSQL will allow this type and the function's first parameter type + * to differ, if there is an existing binary cast between them. That cannot + * be checked at compile time, so a cast with a different type given here + * might successfully compile but fail to deploy in PostgreSQL. + */ + String from() default ""; + + /** + * The target type to cast to. Will default to the return type of + * the associated function, if known. + *

+ * PostgreSQL will allow this type and the function's return type + * to differ, if there is an existing binary cast between them. That cannot + * be checked at compile time, so a cast with a different type given here + * might successfully compile but fail to deploy in PostgreSQL. + */ + String to() default ""; + + /** + * A stock conversion path when a dedicated function is not supplied: + * {@code BINARY} for two types that are known to have the same internal + * representation, or {@code INOUT} to invoke the first type's text-output + * function followed by the second type's text-input function. + *

+ * To declare an {@code INOUT} cast, {@code with=INOUT} must appear + * explicitly; the default value is treated as unspecified. + */ + Path path() default Path.INOUT; + + /** + * When this cast can be applied: only in explicit form, when used in + * assignment context, or implicitly whenever needed. + */ + Application application() default Application.EXPLICIT; + + /** + * One or more arbitrary labels that will be considered 'provided' by the + * object carrying this annotation. The deployment descriptor will be + * generated in such an order that other objects that 'require' labels + * 'provided' by this come later in the output for install actions, and + * earlier for remove actions. + */ + String[] provides() default {}; + + /** + * One or more arbitrary labels that will be considered 'required' by the + * object carrying this annotation. The deployment descriptor will be + * generated in such an order that other objects that 'provide' labels + * 'required' by this come earlier in the output for install actions, and + * later for remove actions. + */ + String[] requires() default {}; + + /** + * The {@code } to be used around SQL code generated + * for this cast. Defaults to {@code PostgreSQL}. Set explicitly to + * {@code ""} to emit code not wrapped in an {@code }. + */ + String implementor() default ""; + + /** + * A comment to be associated with the cast. If left to default, and the + * annotated Java construct has a doc comment, its first sentence will be + * used. If an empty string is explicitly given, no comment will be set. + */ + String comment() default ""; + + /** + * @hidden container type allowing Cast to be repeatable. + */ + @Documented + @Target({ElementType.METHOD, ElementType.TYPE}) + @Retention(RetentionPolicy.CLASS) + @interface Container + { + Cast[] value(); + } +} diff --git a/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLAction.java b/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLAction.java index edbf63feb..face77719 100644 --- a/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLAction.java +++ b/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLAction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2013 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2020 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 @@ -14,6 +14,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -35,6 +36,7 @@ */ @Documented @Target({ElementType.PACKAGE,ElementType.TYPE}) +@Repeatable(SQLActions.class) @Retention(RetentionPolicy.CLASS) public @interface SQLAction { diff --git a/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLActions.java b/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLActions.java index bc618b4a8..753e1df25 100644 --- a/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLActions.java +++ b/pljava-api/src/main/java/org/postgresql/pljava/annotation/SQLActions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2013 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2020 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,7 +21,11 @@ /** * Container for multiple {@link SQLAction} annotations (in case it is * convenient to hang more than one on a given program element). - * + *

+ * This container annotation is documented for historical reasons (it existed + * in PL/Java versions targeting earlier Java versions than 8). In new code, it + * would be more natural to simply hang more than one {@code SQLAction} + * annotation directly on a program element. * @author Thomas Hallgren - pre-Java6 version * @author Chapman Flack (Purdue Mathematics) - updated to Java6, * added SQLActions diff --git a/pljava-api/src/main/java/org/postgresql/pljava/annotation/processing/DDRProcessor.java b/pljava-api/src/main/java/org/postgresql/pljava/annotation/processing/DDRProcessor.java index 63c716702..07805d519 100644 --- a/pljava-api/src/main/java/org/postgresql/pljava/annotation/processing/DDRProcessor.java +++ b/pljava-api/src/main/java/org/postgresql/pljava/annotation/processing/DDRProcessor.java @@ -49,6 +49,7 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -104,6 +105,7 @@ import org.postgresql.pljava.ResultSetProvider; import org.postgresql.pljava.TriggerData; +import org.postgresql.pljava.annotation.Cast; import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; import org.postgresql.pljava.annotation.SQLActions; @@ -214,15 +216,19 @@ class DDRProcessorImpl // Our own annotations // final TypeElement AN_FUNCTION; - final TypeElement AN_SQLACTION; - final TypeElement AN_SQLACTIONS; final TypeElement AN_SQLTYPE; final TypeElement AN_TRIGGER; final TypeElement AN_BASEUDT; final TypeElement AN_MAPPEDUDT; + final TypeElement AN_SQLACTION; + final TypeElement AN_SQLACTIONS; + final TypeElement AN_CAST; + final TypeElement AN_CASTS; // Certain familiar DBTypes (capitalized as this file historically has) // + final DBType DT_BOOLEAN = new DBType.Reserved("boolean"); + final DBType DT_INTEGER = new DBType.Reserved("integer"); final DBType DT_RECORD = new DBType.Named( Identifier.Qualified.nameFromJava("pg_catalog.RECORD")); final DBType DT_TRIGGER = new DBType.Named( @@ -234,8 +240,7 @@ class DDRProcessorImpl // final DBType[] SIG_TYPMODIN = { DBType.fromSQLTypeAnnotation("pg_catalog.cstring[]") }; - final DBType[] SIG_TYPMODOUT = - { DBType.fromSQLTypeAnnotation("integer") }; + final DBType[] SIG_TYPMODOUT = { DT_INTEGER }; final DBType[] SIG_ANALYZE = { DBType.fromSQLTypeAnnotation("pg_catalog.internal") }; @@ -309,12 +314,18 @@ class DDRProcessorImpl TY_VOID = typu.getNoType( TypeKind.VOID); AN_FUNCTION = elmu.getTypeElement( Function.class.getName()); - AN_SQLACTION = elmu.getTypeElement( SQLAction.class.getName()); - AN_SQLACTIONS = elmu.getTypeElement( SQLActions.class.getName()); AN_SQLTYPE = elmu.getTypeElement( SQLType.class.getName()); AN_TRIGGER = elmu.getTypeElement( Trigger.class.getName()); AN_BASEUDT = elmu.getTypeElement( BaseUDT.class.getName()); AN_MAPPEDUDT = elmu.getTypeElement( MappedUDT.class.getName()); + + // Repeatable annotations and their containers. + // + AN_SQLACTION = elmu.getTypeElement( SQLAction.class.getName()); + AN_SQLACTIONS = elmu.getTypeElement( SQLActions.class.getName()); + AN_CAST = elmu.getTypeElement( Cast.class.getName()); + AN_CASTS = elmu.getTypeElement( + Cast.Container.class.getCanonicalName()); } void msg( Kind kind, String fmt, Object... args) @@ -377,7 +388,13 @@ public int hashCode() * one round), keyed by the object for which each snippet has been * generated. */ - Map snippets = new HashMap<>(); + /* + * This is a LinkedHashMap so that the order of handling annotation types + * in process() below will be preserved in calling their characterize() + * methods at end-of-round, and so, for example, characterize() on a Cast + * can use values set by characterize() on an associated Function. + */ + Map snippets = new LinkedHashMap<>(); S getSnippet(Object o, Class c, Supplier ctor) { @@ -419,9 +436,9 @@ boolean process( Set tes, RoundEnvironment re) { boolean functionPresent = false; boolean sqlActionPresent = false; - boolean sqlActionsPresent = false; boolean baseUDTPresent = false; boolean mappedUDTPresent = false; + boolean castPresent = false; boolean willClaim = true; @@ -429,16 +446,16 @@ boolean process( Set tes, RoundEnvironment re) { if ( AN_FUNCTION.equals( te) ) functionPresent = true; - else if ( AN_SQLACTION.equals( te) ) - sqlActionPresent = true; - else if ( AN_SQLACTIONS.equals( te) ) - sqlActionsPresent = true; else if ( AN_BASEUDT.equals( te) ) baseUDTPresent = true; else if ( AN_MAPPEDUDT.equals( te) ) mappedUDTPresent = true; else if ( AN_SQLTYPE.equals( te) ) ; // these are handled within FunctionImpl + else if ( AN_SQLACTION.equals( te) || AN_SQLACTIONS.equals( te) ) + sqlActionPresent = true; + else if ( AN_CAST.equals( te) || AN_CASTS.equals( te) ) + castPresent = true; else { msg( Kind.WARNING, te, @@ -461,14 +478,18 @@ else if ( AN_SQLTYPE.equals( te) ) processFunction( e); if ( sqlActionPresent ) - for ( Element e : re.getElementsAnnotatedWith( AN_SQLACTION) ) - processSQLAction( e); - - if ( sqlActionsPresent ) - for ( Element e : re.getElementsAnnotatedWith( AN_SQLACTIONS) ) - processSQLActions( e); + for ( Element e + : re.getElementsAnnotatedWithAny( AN_SQLACTION, AN_SQLACTIONS) ) + processRepeatable( + e, AN_SQLACTION, AN_SQLACTIONS, SQLActionImpl.class); + + if ( castPresent ) + for ( Element e + : re.getElementsAnnotatedWithAny( AN_CAST, AN_CASTS) ) + processRepeatable( + e, AN_CAST, AN_CASTS, CastImpl.class); - tmpr.workAroundJava7Breakage(); // perhaps it will be fixed in Java 9? + tmpr.workAroundJava7Breakage(); // perhaps to be fixed in Java 9? nope. if ( ! re.processingOver() ) defensiveEarlyCharacterize(); @@ -739,34 +760,44 @@ Snippet[] order( } /** - * Process a single element annotated with @SQLAction. - */ - void processSQLAction( Element e) - { - SQLActionImpl sa = - getSnippet( e, SQLActionImpl.class, SQLActionImpl::new); - for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) ) - { - if ( am.getAnnotationType().asElement().equals( AN_SQLACTION) ) - populateAnnotationImpl( sa, e, am); - } - } - - /** - * Process a single element annotated with @SQLActions (which simply takes - * an array of @SQLAction as a way to associate more than one SQLAction with - * a single program element).. + * Process an element carrying a repeatable annotation, the container + * of that repeatable annotation, or both. + *

+ * Snippets corresponding to repeatable annotations are not entered in the + * {@code snippets} map keyed by the target element, as that might not be + * unique. Each snippet is entered with a key made from its class and + * itself. They are not expected to be looked up, only processed when all of + * the map entries are enumerated. */ - void processSQLActions( Element e) + void processRepeatable( + Element e, TypeElement annot, TypeElement container, Class clazz) { for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) ) { - if ( am.getAnnotationType().asElement().equals( AN_SQLACTIONS) ) + Element asElement = am.getAnnotationType().asElement(); + if ( asElement.equals( annot) ) { - SQLActionsImpl sas = new SQLActionsImpl(); - populateAnnotationImpl( sas, e, am); - for ( SQLAction sa : sas.value() ) - putSnippet( sa, (Snippet)sa); + T snip; + try + { + snip = clazz.getDeclaredConstructor( DDRProcessorImpl.class, + Element.class, AnnotationMirror.class) + .newInstance( DDRProcessorImpl.this, e, am); + } + catch ( ReflectiveOperationException re ) + { + throw new RuntimeException( + "Incorrect implementation of annotation processor", re); + } + populateAnnotationImpl( snip, e, am); + putSnippet( snip, (Snippet)snip); + } + else if ( asElement.equals( container) ) + { + Container c = new Container<>(clazz); + populateAnnotationImpl( c, e, am); + for ( T snip : c.value() ) + putSnippet( snip, (Snippet)snip); } } } @@ -1098,6 +1129,18 @@ public Set requireTags() } } + class Repeatable extends AbstractAnnotationImpl + { + final Element m_targetElement; + final AnnotationMirror m_origin; + + Repeatable(Element e, AnnotationMirror am) + { + m_targetElement = e; + m_origin = am; + } + } + /** * Populate an AbstractAnnotationImpl-derived Annotation implementation * from the element-value pairs in an AnnotationMirror. For each element @@ -1265,30 +1308,56 @@ public void setName( Object o, boolean explicit, Element e) } } - class SQLActionsImpl extends AbstractAnnotationImpl implements SQLActions + class Container + extends AbstractAnnotationImpl { - public SQLAction[] value() { return _value; } + public T[] value() { return _value; } - SQLAction[] _value; + T[] _value; + final Class _clazz; + + Container(Class clazz) + { + _clazz = clazz; + } public void setValue( Object o, boolean explicit, Element e) { AnnotationMirror[] ams = avToArray( o, AnnotationMirror.class); - _value = new SQLAction [ ams.length ]; + + @SuppressWarnings("unchecked") + T[] t = (T[])Array.newInstance( _clazz, ams.length); + _value = t; + int i = 0; for ( AnnotationMirror am : ams ) { - SQLActionImpl a = new SQLActionImpl(); - populateAnnotationImpl( a, e, am); - _value [ i++ ] = a; + try + { + T a = _clazz.getDeclaredConstructor(DDRProcessorImpl.class, + Element.class, AnnotationMirror.class) + .newInstance(DDRProcessorImpl.this, e, am); + populateAnnotationImpl( a, e, am); + _value [ i++ ] = a; + } + catch ( ReflectiveOperationException re ) + { + throw new RuntimeException( + "Incorrect implementation of annotation processor", re); + } } } } class SQLActionImpl - extends AbstractAnnotationImpl + extends Repeatable implements SQLAction, Snippet { + SQLActionImpl(Element e, AnnotationMirror am) + { + super(e, am); + } + public String[] install() { return _install; } public String[] remove() { return _remove; } public String[] provides() { return _provides; } @@ -1528,7 +1597,7 @@ public String[] deployStrings() if ( ! "".equals( _when) ) sb.append( "\n\tWHEN ").append( _when); sb.append( "\n\tEXECUTE PROCEDURE "); - func.appendNameAndParams( sb, false); + func.appendNameAndParams( sb, true, false); sb.setLength( sb.length() - 1); // drop closing ) s = _arguments.length; for ( String a : _arguments ) @@ -1916,15 +1985,15 @@ public void subsume() * * @param dflts Whether to include the defaults, if any. */ - void appendNameAndParams( StringBuilder sb, boolean dflts) + void appendNameAndParams(StringBuilder sb, boolean names, boolean dflts) { sb.append(qnameFrom(name(), schema())).append( '('); - appendParams( sb, dflts); + appendParams( sb, names, dflts); // TriggerImpl relies on ) being the very last character sb.append( ')'); } - void appendParams( StringBuilder sb, boolean dflts) + void appendParams( StringBuilder sb, boolean names, boolean dflts) { int count = parameterTypes.length; for ( ParameterInfo i @@ -1941,7 +2010,10 @@ void appendParams( StringBuilder sb, boolean dflts) if ( _variadic && 0 == count ) sb.append("VARIADIC "); - sb.append(name).append(' ').append(i.dt.toString(dflts)); + if ( names ) + sb.append(name).append(' '); + + sb.append(i.dt.toString(dflts)); if ( 0 < count ) sb.append(','); @@ -1966,7 +2038,7 @@ public String[] deployStrings() ArrayList al = new ArrayList<>(); StringBuilder sb = new StringBuilder(); sb.append( "CREATE OR REPLACE FUNCTION "); - appendNameAndParams( sb, true); + appendNameAndParams( sb, true, true); sb.append( "\n\tRETURNS "); if ( trigger ) sb.append( DT_TRIGGER.toString()); @@ -2004,7 +2076,7 @@ public String[] deployStrings() { sb.setLength( 0); sb.append( "COMMENT ON FUNCTION "); - appendNameAndParams( sb, false); + appendNameAndParams( sb, true, false); sb.append( "\nIS "); sb.append( DDRWriter.eQuote( comm)); al.add( sb.toString()); @@ -2029,7 +2101,7 @@ public String[] undeployStrings() StringBuilder sb = new StringBuilder(); sb.append( "DROP FUNCTION "); - appendNameAndParams( sb, false); + appendNameAndParams( sb, true, false); rslt [ rslt.length - 1 ] = sb.toString(); return rslt; } @@ -2138,7 +2210,7 @@ class BaseUDTFunctionImpl extends FunctionImpl BaseUDTFunctionID id; @Override - void appendParams( StringBuilder sb, boolean dflts) + void appendParams( StringBuilder sb, boolean names, boolean dflts) { sb.append( Arrays.stream(id.getParam( ui)) @@ -2700,6 +2772,216 @@ public Vertex breakCycle(Vertex v, boolean deploy) } } + class CastImpl + extends Repeatable + implements Cast, Snippet, Commentable + { + CastImpl(Element e, AnnotationMirror am) + { + super(e, am); + } + + public String from() { return _from; } + public String to() { return _to; } + public Cast.Path path() { return _path; } + public Cast.Application application() { return _application; } + public String[] provides() { return _provides; } + public String[] requires() { return _requires; } + + public String _from; + public String _to; + public Cast.Path _path; + public Cast.Application _application; + public String[] _provides; + public String[] _requires; + + FunctionImpl func; + DBType fromType; + DBType toType; + + public void setPath( Object o, boolean explicit, Element e) + { + if ( explicit ) + _path = Path.valueOf( + ((VariableElement)o).getSimpleName().toString()); + } + + public boolean characterize() + { + boolean ok = true; + + if ( ElementKind.METHOD.equals(m_targetElement.getKind()) ) + { + func = getSnippet(m_targetElement, FunctionImpl.class, + () -> (FunctionImpl)null); + if ( null == func ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "A method annotated with @Cast must also have @Function" + ); + ok = false; + } + } + + if ( null == func && "".equals(_from) ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "@Cast not annotating a method must specify from=" + ); + ok = false; + } + + if ( null == func && "".equals(_to) ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "@Cast not annotating a method must specify to=" + ); + ok = false; + } + + if ( null == func && null == _path ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "@Cast not annotating a method, and without path=, " + + "is not yet supported" + ); + ok = false; + } + + if ( ok ) + { + fromType = ("".equals(_from)) + ? func.parameterTypes[0] + : DBType.fromSQLTypeAnnotation(_from); + + toType = ("".equals(_to)) + ? func.returnType + : DBType.fromSQLTypeAnnotation(_to); + } + + if ( null != _path ) + { + if ( ok && Path.BINARY == _path && fromType.equals(toType) ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "A cast with from and to types the same can only " + + "apply a type modifier; path=BINARY will have " + + "no effect"); + ok = false; + } + } + else if ( null != func ) + { + int nparams = func.parameterTypes.length; + + if ( ok && 2 > nparams && fromType.equals(toType) ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "A cast with from and to types the same can only " + + "apply a type modifier, therefore must have at least " + + "two parameters"); + ok = false; + } + + if ( 1 > nparams || nparams > 3 ) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "A cast function must have 1, 2, or 3 parameters"); + ok = false; + } + + if (1 < nparams && ! DT_INTEGER.equals(func.parameterTypes[1])) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "Parameter 2 of a cast function must have integer type" + ); + ok = false; + } + + if (3 == nparams && ! DT_BOOLEAN.equals(func.parameterTypes[2])) + { + msg(Kind.ERROR, m_targetElement, m_origin, + "Parameter 3 of a cast function must have boolean type" + ); + ok = false; + } + } + + if ( ! ok ) + return false; + + recordImplicitTags(); + recordExplicitTags(_provides, _requires); + return true; + } + + void recordImplicitTags() + { + Set requires = requireTags(); + + DependTag dt = fromType.dependTag(); + if ( null != dt ) + requires.add(dt); + + dt = toType.dependTag(); + if ( null != dt ) + requires.add(dt); + + if ( null == _path ) + { + dt = func.provideTags().stream() + .filter(DependTag.Function.class::isInstance) + .findAny().get(); + requires.add(dt); + } + } + + public String[] deployStrings() + { + List al = new ArrayList<>(); + + StringBuilder sb = new StringBuilder(); + + sb.append("CREATE CAST (") + .append(fromType).append(" AS ").append(toType).append(")\n\t"); + + if ( Path.BINARY == _path ) + sb.append("WITHOUT FUNCTION"); + else if ( Path.INOUT == _path ) + sb.append("WITH INOUT"); + else + { + sb.append("WITH FUNCTION "); + func.appendNameAndParams(sb, false, false); + } + + switch ( _application ) + { + case ASSIGNMENT: sb.append("\n\tAS ASSIGNMENT"); break; + case EXPLICIT: break; + case IMPLICIT: sb.append("\n\tAS IMPLICIT"); + } + + al.add(sb.toString()); + + if ( null != comment() ) + al.add( + "COMMENT ON CAST (" + + fromType + " AS " + toType + ") IS " + + DDRWriter.eQuote(comment())); + + return al.toArray( new String [ al.size() ]); + } + + public String[] undeployStrings() + { + return new String[] + { + "DROP CAST (" + fromType + " AS " + toType + ")" + }; + } + } + /** * Provides the default mappings from Java types to SQL types. */ diff --git a/pljava-api/src/test/java/LexicalsTest.java b/pljava-api/src/test/java/LexicalsTest.java index ec58a2a45..e12ca9674 100644 --- a/pljava-api/src/test/java/LexicalsTest.java +++ b/pljava-api/src/test/java/LexicalsTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; import static org.postgresql.pljava.sqlgen.Lexicals.ISO_AND_PG_IDENTIFIER_CAPTURING; diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/ConditionalDDR.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/ConditionalDDR.java index ac26d8a95..0140f4372 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/ConditionalDDR.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/ConditionalDDR.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015- Tada AB and other contributors, as listed below. + * Copyright (c) 2015-2020 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 @@ -12,7 +12,6 @@ package org.postgresql.pljava.example.annotation; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; /** * Test of a very simple form of conditional execution in the deployment @@ -68,71 +67,69 @@ * several statements setting PostgreSQL-version-based implementor tags that * are relied on by various other examples in this directory. */ -@SQLActions({ - @SQLAction(provides={"LifeIsGood","LifeIsNotGood"}, install= - "SELECT CASE 42 WHEN 42 THEN " + - " set_config('pljava.implementors', 'LifeIsGood,' || " + - " current_setting('pljava.implementors'), true) " + - "ELSE " + - " set_config('pljava.implementors', 'LifeIsNotGood,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), +@SQLAction(provides={"LifeIsGood","LifeIsNotGood"}, install= + "SELECT CASE 42 WHEN 42 THEN " + + " set_config('pljava.implementors', 'LifeIsGood,' || " + + " current_setting('pljava.implementors'), true) " + + "ELSE " + + " set_config('pljava.implementors', 'LifeIsNotGood,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) - @SQLAction(implementor="LifeIsGood", install= - "SELECT javatest.logmessage('INFO', 'Looking good!')" - ), +@SQLAction(implementor="LifeIsGood", install= + "SELECT javatest.logmessage('INFO', 'Looking good!')" +) - @SQLAction(implementor="LifeIsNotGood", install= - "SELECT javatest.logmessage('WARNING', 'This should not be executed')" - ), +@SQLAction(implementor="LifeIsNotGood", install= + "SELECT javatest.logmessage('WARNING', 'This should not be executed')" +) - @SQLAction(provides="postgresql_ge_80300", install= - "SELECT CASE WHEN" + - " 80300 <= CAST(current_setting('server_version_num') AS integer)" + - " THEN set_config('pljava.implementors', 'postgresql_ge_80300,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), +@SQLAction(provides="postgresql_ge_80300", install= + "SELECT CASE WHEN" + + " 80300 <= CAST(current_setting('server_version_num') AS integer)" + + " THEN set_config('pljava.implementors', 'postgresql_ge_80300,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) - @SQLAction(provides="postgresql_ge_80400", install= - "SELECT CASE WHEN" + - " 80400 <= CAST(current_setting('server_version_num') AS integer)" + - " THEN set_config('pljava.implementors', 'postgresql_ge_80400,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), +@SQLAction(provides="postgresql_ge_80400", install= + "SELECT CASE WHEN" + + " 80400 <= CAST(current_setting('server_version_num') AS integer)" + + " THEN set_config('pljava.implementors', 'postgresql_ge_80400,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) - @SQLAction(provides="postgresql_ge_90000", install= - "SELECT CASE WHEN" + - " 90000 <= CAST(current_setting('server_version_num') AS integer)" + - " THEN set_config('pljava.implementors', 'postgresql_ge_90000,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), +@SQLAction(provides="postgresql_ge_90000", install= + "SELECT CASE WHEN" + + " 90000 <= CAST(current_setting('server_version_num') AS integer)" + + " THEN set_config('pljava.implementors', 'postgresql_ge_90000,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) - @SQLAction(provides="postgresql_ge_90100", install= - "SELECT CASE WHEN" + - " 90100 <= CAST(current_setting('server_version_num') AS integer)" + - " THEN set_config('pljava.implementors', 'postgresql_ge_90100,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), +@SQLAction(provides="postgresql_ge_90100", install= + "SELECT CASE WHEN" + + " 90100 <= CAST(current_setting('server_version_num') AS integer)" + + " THEN set_config('pljava.implementors', 'postgresql_ge_90100,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) - @SQLAction(provides="postgresql_ge_90300", install= - "SELECT CASE WHEN" + - " 90300 <= CAST(current_setting('server_version_num') AS integer)" + - " THEN set_config('pljava.implementors', 'postgresql_ge_90300,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), +@SQLAction(provides="postgresql_ge_90300", install= + "SELECT CASE WHEN" + + " 90300 <= CAST(current_setting('server_version_num') AS integer)" + + " THEN set_config('pljava.implementors', 'postgresql_ge_90300,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) - @SQLAction(provides="postgresql_ge_100000", install= - "SELECT CASE WHEN" + - " 100000 <= CAST(current_setting('server_version_num') AS integer)" + - " THEN set_config('pljava.implementors', 'postgresql_ge_100000,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), -}) +@SQLAction(provides="postgresql_ge_100000", install= + "SELECT CASE WHEN" + + " 100000 <= CAST(current_setting('server_version_num') AS integer)" + + " THEN set_config('pljava.implementors', 'postgresql_ge_100000,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) public class ConditionalDDR { } diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Enumeration.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Enumeration.java index 298050537..2fcee7df1 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Enumeration.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Enumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015- Tada AB and other contributors, as listed below. + * Copyright (c) 2015-2020 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 @@ -15,7 +15,6 @@ import java.util.Arrays; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.annotation.SQLType; import org.postgresql.pljava.annotation.Function; @@ -27,21 +26,19 @@ * version, set up in the {@link ConditionalDDR} example. PostgreSQL before 8.3 * did not have enum types. */ -@SQLActions({ - @SQLAction(provides="mood type", implementor="postgresql_ge_80300", - install="CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')", - remove="DROP TYPE mood" - ), - @SQLAction(implementor="postgresql_ge_80300", - requires={"textToMood", "moodToText", "textsToMoods", "moodsToTexts"}, - install={ - "SELECT textToMood('happy')", - "SELECT moodToText('happy'::mood)", - "SELECT textsToMoods(array['happy','happy','sad','ok'])", - "SELECT moodsToTexts(array['happy','happy','sad','ok']::mood[])" - } - ) -}) +@SQLAction(provides="mood type", implementor="postgresql_ge_80300", + install="CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')", + remove="DROP TYPE mood" +) +@SQLAction(implementor="postgresql_ge_80300", + requires={"textToMood", "moodToText", "textsToMoods", "moodsToTexts"}, + install={ + "SELECT textToMood('happy')", + "SELECT moodToText('happy'::mood)", + "SELECT textsToMoods(array['happy','happy','sad','ok'])", + "SELECT moodsToTexts(array['happy','happy','sad','ok']::mood[])" + } +) public class Enumeration { @Function(requires="mood type", provides="textToMood", type="mood", diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/IntWithMod.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/IntWithMod.java index c28215616..a31928a62 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/IntWithMod.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/IntWithMod.java @@ -21,6 +21,7 @@ import java.sql.SQLOutput; import java.sql.Statement; +import org.postgresql.pljava.annotation.Cast; import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; import org.postgresql.pljava.annotation.SQLType; @@ -51,32 +52,13 @@ *

* Of course this example more or less duplicates what you could do in two lines * with CREATE DOMAIN. But it is enough to illustrate the process. - *

- * Certainly, it would be less tedious with some more annotation support and - * autogeneration of the ordering dependencies that are now added by hand here. - *

- * Most of this must be suppressed (using conditional implementor tags) if the - * PostgreSQL instance is older than 8.3, because it won't have the cstring[] - * type, so the typeModifierInput function can't be declared, and so neither - * can the type, or functions that accept or return it. See the - * {@link ConditionalDDR} example for where the implementor tag is set up. */ -@SQLAction(requires={"IntWithMod type", "IntWithMod modApply"}, - implementor="postgresql_ge_80300", - remove="DROP CAST (javatest.IntWithMod AS javatest.IntWithMod)", +@SQLAction(requires="IntWithMod modCast", install={ - "CREATE CAST (javatest.IntWithMod AS javatest.IntWithMod)" + - " WITH FUNCTION javatest.intwithmod_typmodapply(" + - " javatest.IntWithMod, integer, boolean)", - - "COMMENT ON CAST (javatest.IntWithMod AS javatest.IntWithMod) IS '" + - "Cast that applies/verifies the type modifier on an IntWithMod.'", - "SELECT CAST('42' AS javatest.IntWithMod(even))" } ) @BaseUDT(schema="javatest", provides="IntWithMod type", - implementor="postgresql_ge_80300", typeModifierInput="javatest.intwithmod_typmodin", typeModifierOutput="javatest.intwithmod_typmodout", like="pg_catalog.int4") @@ -146,7 +128,6 @@ public void writeSQL(SQLOutput stream) throws SQLException { * "even" or "odd". The modifier value is 0 for even or 1 for odd. */ @Function(schema="javatest", name="intwithmod_typmodin", - implementor="postgresql_ge_80300", effects=IMMUTABLE, onNullInput=RETURNS_NULL) public static int modIn(@SQLType("pg_catalog.cstring[]") String[] toks) throws SQLException { @@ -180,9 +161,10 @@ public static String modOut(int mod) throws SQLException { * Function backing the type-modifier application cast for IntWithMod type. */ @Function(schema="javatest", name="intwithmod_typmodapply", - implementor="postgresql_ge_80300", - provides="IntWithMod modApply", effects=IMMUTABLE, onNullInput=RETURNS_NULL) + @Cast(comment= + "Cast that applies/verifies the type modifier on an IntWithMod.", + provides="IntWithMod modCast") public static IntWithMod modApply(IntWithMod iwm, int mod, boolean explicit) throws SQLException { diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java index 768726173..212cb13ba 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java @@ -13,7 +13,6 @@ import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.example.annotation.ConditionalDDR; // for javadoc @@ -27,102 +26,100 @@ * Relies on PostgreSQL-version-specific implementor tags set up in the * {@link ConditionalDDR} example. */ -@SQLActions({ - @SQLAction( - implementor="postgresql_ge_90300",requires="TypeRoundTripper.roundTrip", - install={ - " SELECT" + - " CASE WHEN every(orig = roundtripped)" + - " THEN javatest.logmessage('INFO', 'java.time.LocalDate passes')" + - " ELSE javatest.logmessage('WARNING', 'java.time.LocalDate fails')" + - " END" + - " FROM" + - " (VALUES" + - " (date '2017-08-21')," + - " (date '1970-03-07')," + - " (date '1919-05-29')" + - " ) AS p(orig)," + - " javatest.roundtrip(p, 'java.time.LocalDate')" + - " AS r(roundtripped date)", +@SQLAction( + implementor="postgresql_ge_90300",requires="TypeRoundTripper.roundTrip", + install={ + " SELECT" + + " CASE WHEN every(orig = roundtripped)" + + " THEN javatest.logmessage('INFO', 'java.time.LocalDate passes')" + + " ELSE javatest.logmessage('WARNING', 'java.time.LocalDate fails')" + + " END" + + " FROM" + + " (VALUES" + + " (date '2017-08-21')," + + " (date '1970-03-07')," + + " (date '1919-05-29')" + + " ) AS p(orig)," + + " javatest.roundtrip(p, 'java.time.LocalDate')" + + " AS r(roundtripped date)", - " SELECT" + - " CASE WHEN every(orig = roundtripped)" + - " THEN javatest.logmessage('INFO', 'java.time.LocalTime passes')" + - " ELSE javatest.logmessage('WARNING', 'java.time.LocalTime fails')" + - " END" + - " FROM" + - " (SELECT current_time::time) AS p(orig)," + - " javatest.roundtrip(p, 'java.time.LocalTime')" + - " AS r(roundtripped time)", + " SELECT" + + " CASE WHEN every(orig = roundtripped)" + + " THEN javatest.logmessage('INFO', 'java.time.LocalTime passes')" + + " ELSE javatest.logmessage('WARNING', 'java.time.LocalTime fails')" + + " END" + + " FROM" + + " (SELECT current_time::time) AS p(orig)," + + " javatest.roundtrip(p, 'java.time.LocalTime')" + + " AS r(roundtripped time)", - " SELECT" + - " CASE WHEN every(orig = roundtripped)" + - " THEN javatest.logmessage('INFO', 'java.time.OffsetTime passes')" + - " ELSE javatest.logmessage('WARNING', 'java.time.OffsetTime fails')" + - " END" + - " FROM" + - " (SELECT current_time::timetz) AS p(orig)," + - " javatest.roundtrip(p, 'java.time.OffsetTime')" + - " AS r(roundtripped timetz)", + " SELECT" + + " CASE WHEN every(orig = roundtripped)" + + " THEN javatest.logmessage('INFO', 'java.time.OffsetTime passes')" + + " ELSE javatest.logmessage('WARNING', 'java.time.OffsetTime fails')" + + " END" + + " FROM" + + " (SELECT current_time::timetz) AS p(orig)," + + " javatest.roundtrip(p, 'java.time.OffsetTime')" + + " AS r(roundtripped timetz)", - " SELECT" + - " CASE WHEN every(orig = roundtripped)" + - " THEN javatest.logmessage('INFO', 'java.time.LocalDateTime passes')" + - " ELSE javatest.logmessage('WARNING','java.time.LocalDateTime fails')"+ - " END" + - " FROM" + - " (SELECT 'on' = current_setting('integer_datetimes')) AS ck(idt)," + - " LATERAL (" + - " SELECT" + - " value" + - " FROM" + - " (VALUES" + - " (true, timestamp '2017-08-21 18:25:29.900005')," + - " (true, timestamp '1970-03-07 17:37:49.300009')," + - " (true, timestamp '1919-05-29 13:08:33.600001')," + - " (idt, timestamp 'infinity')," + - " (idt, timestamp '-infinity')" + - " ) AS vs(cond, value)" + - " WHERE cond" + - " ) AS p(orig)," + - " javatest.roundtrip(p, 'java.time.LocalDateTime')" + - " AS r(roundtripped timestamp)", + " SELECT" + + " CASE WHEN every(orig = roundtripped)" + + " THEN javatest.logmessage('INFO', 'java.time.LocalDateTime passes')" + + " ELSE javatest.logmessage('WARNING','java.time.LocalDateTime fails')"+ + " END" + + " FROM" + + " (SELECT 'on' = current_setting('integer_datetimes')) AS ck(idt)," + + " LATERAL (" + + " SELECT" + + " value" + + " FROM" + + " (VALUES" + + " (true, timestamp '2017-08-21 18:25:29.900005')," + + " (true, timestamp '1970-03-07 17:37:49.300009')," + + " (true, timestamp '1919-05-29 13:08:33.600001')," + + " (idt, timestamp 'infinity')," + + " (idt, timestamp '-infinity')" + + " ) AS vs(cond, value)" + + " WHERE cond" + + " ) AS p(orig)," + + " javatest.roundtrip(p, 'java.time.LocalDateTime')" + + " AS r(roundtripped timestamp)", - " SELECT" + - " CASE WHEN every(orig = roundtripped)" + - " THEN javatest.logmessage('INFO', 'java.time.OffsetDateTime passes')"+ - " ELSE javatest.logmessage(" + - " 'WARNING','java.time.OffsetDateTime fails')"+ - " END" + - " FROM" + - " (SELECT 'on' = current_setting('integer_datetimes')) AS ck(idt)," + - " LATERAL (" + - " SELECT" + - " value" + - " FROM" + - " (VALUES" + - " (true, timestamptz '2017-08-21 18:25:29.900005Z')," + - " (true, timestamptz '1970-03-07 17:37:49.300009Z')," + - " (true, timestamptz '1919-05-29 13:08:33.600001Z')," + - " (idt, timestamptz 'infinity')," + - " (idt, timestamptz '-infinity')" + - " ) AS vs(cond, value)" + - " WHERE cond" + - " ) AS p(orig)," + - " javatest.roundtrip(p, 'java.time.OffsetDateTime')" + - " AS r(roundtripped timestamptz)", + " SELECT" + + " CASE WHEN every(orig = roundtripped)" + + " THEN javatest.logmessage('INFO', 'java.time.OffsetDateTime passes')"+ + " ELSE javatest.logmessage(" + + " 'WARNING','java.time.OffsetDateTime fails')"+ + " END" + + " FROM" + + " (SELECT 'on' = current_setting('integer_datetimes')) AS ck(idt)," + + " LATERAL (" + + " SELECT" + + " value" + + " FROM" + + " (VALUES" + + " (true, timestamptz '2017-08-21 18:25:29.900005Z')," + + " (true, timestamptz '1970-03-07 17:37:49.300009Z')," + + " (true, timestamptz '1919-05-29 13:08:33.600001Z')," + + " (idt, timestamptz 'infinity')," + + " (idt, timestamptz '-infinity')" + + " ) AS vs(cond, value)" + + " WHERE cond" + + " ) AS p(orig)," + + " javatest.roundtrip(p, 'java.time.OffsetDateTime')" + + " AS r(roundtripped timestamptz)", - " SELECT" + - " CASE WHEN every(orig = roundtripped)" + - " THEN javatest.logmessage('INFO', 'OffsetTime as stmt param passes')"+ - " ELSE javatest.logmessage(" + - " 'WARNING','java.time.OffsetTime as stmt param fails')"+ - " END" + - " FROM" + - " (SELECT current_time::timetz) AS p(orig)," + - " javatest.roundtrip(p, 'java.time.OffsetTime', true)" + - " AS r(roundtripped timetz)" - }) + " SELECT" + + " CASE WHEN every(orig = roundtripped)" + + " THEN javatest.logmessage('INFO', 'OffsetTime as stmt param passes')"+ + " ELSE javatest.logmessage(" + + " 'WARNING','java.time.OffsetTime as stmt param fails')"+ + " END" + + " FROM" + + " (SELECT current_time::timetz) AS p(orig)," + + " javatest.roundtrip(p, 'java.time.OffsetTime', true)" + + " AS r(roundtripped timetz)" }) public class JDBC42_21 { diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java index e5614933b..bd22169e3 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java @@ -67,7 +67,6 @@ import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.MappedUDT; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.annotation.SQLType; import static org.postgresql.pljava.example.LoggerTest.logMessage; @@ -105,152 +104,150 @@ * Everything mentioning the type XML here needs a conditional implementor tag * in case of being loaded into a PostgreSQL instance built without that type. */ -@SQLActions({ - @SQLAction(provides="postgresql_xml", install= - "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + - " THEN set_config('pljava.implementors', 'postgresql_xml,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), - - @SQLAction(implementor="postgresql_ge_80400", - provides="postgresql_xml_ge84", - install= - "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + - " THEN set_config('pljava.implementors', 'postgresql_xml_ge84,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ), - - @SQLAction(implementor="postgresql_xml_ge84", requires="echoXMLParameter", - install= - "WITH" + - " s(how) AS (SELECT generate_series(1, 7))," + - " t(x) AS (" + - " SELECT table_to_xml('pg_catalog.pg_operator', true, false, '')" + - " )," + - " r(howin, howout, isdoc) AS (" + - " SELECT" + - " i.how, o.how," + - " javatest.echoxmlparameter(x, i.how, o.how) IS DOCUMENT" + - " FROM" + - " t, s AS i, s AS o" + - " WHERE" + - " NOT (i.how = 6 and o.how = 7)" + // 6->7 unreliable in some JREs - " ) " + - "SELECT" + - " CASE WHEN every(isdoc)" + - " THEN javatest.logmessage('INFO', 'SQLXML echos succeeded')" + - " ELSE javatest.logmessage('WARNING', 'SQLXML echos had problems')" + - " END " + - "FROM" + - " r" - ), - - @SQLAction(implementor="postgresql_xml_ge84", requires="proxiedXMLEcho", - install= - "WITH" + - " s(how) AS (SELECT unnest('{1,2,4,5,6,7}'::int[]))," + - " t(x) AS (" + - " SELECT table_to_xml('pg_catalog.pg_operator', true, false, '')" + - " )," + - " r(how, isdoc) AS (" + - " SELECT" + - " how," + - " javatest.proxiedxmlecho(x, how) IS DOCUMENT" + - " FROM" + - " t, s" + - " )" + - "SELECT" + - " CASE WHEN every(isdoc)" + - " THEN javatest.logmessage('INFO', 'proxied SQLXML echos succeeded')" + - " ELSE javatest.logmessage('WARNING'," + - " 'proxied SQLXML echos had problems')" + - " END " + - "FROM" + - " r" - ), - - @SQLAction(implementor="postgresql_xml_ge84", requires="lowLevelXMLEcho", - install={ +@SQLAction(provides="postgresql_xml", install= + "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + + " THEN set_config('pljava.implementors', 'postgresql_xml,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) + +@SQLAction(implementor="postgresql_ge_80400", + provides="postgresql_xml_ge84", + install= + "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + + " THEN set_config('pljava.implementors', 'postgresql_xml_ge84,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) + +@SQLAction(implementor="postgresql_xml_ge84", requires="echoXMLParameter", + install= + "WITH" + + " s(how) AS (SELECT generate_series(1, 7))," + + " t(x) AS (" + + " SELECT table_to_xml('pg_catalog.pg_operator', true, false, '')" + + " )," + + " r(howin, howout, isdoc) AS (" + + " SELECT" + + " i.how, o.how," + + " javatest.echoxmlparameter(x, i.how, o.how) IS DOCUMENT" + + " FROM" + + " t, s AS i, s AS o" + + " WHERE" + + " NOT (i.how = 6 and o.how = 7)" + // 6->7 unreliable in some JREs + " ) " + + "SELECT" + + " CASE WHEN every(isdoc)" + + " THEN javatest.logmessage('INFO', 'SQLXML echos succeeded')" + + " ELSE javatest.logmessage('WARNING', 'SQLXML echos had problems')" + + " END " + + "FROM" + + " r" +) + +@SQLAction(implementor="postgresql_xml_ge84", requires="proxiedXMLEcho", + install= + "WITH" + + " s(how) AS (SELECT unnest('{1,2,4,5,6,7}'::int[]))," + + " t(x) AS (" + + " SELECT table_to_xml('pg_catalog.pg_operator', true, false, '')" + + " )," + + " r(how, isdoc) AS (" + + " SELECT" + + " how," + + " javatest.proxiedxmlecho(x, how) IS DOCUMENT" + + " FROM" + + " t, s" + + " )" + + "SELECT" + + " CASE WHEN every(isdoc)" + + " THEN javatest.logmessage('INFO', 'proxied SQLXML echos succeeded')" + + " ELSE javatest.logmessage('WARNING'," + + " 'proxied SQLXML echos had problems')" + + " END " + + "FROM" + + " r" +) + +@SQLAction(implementor="postgresql_xml_ge84", requires="lowLevelXMLEcho", + install={ + "SELECT" + + " preparexmlschema('schematest', $$" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "" + + "$$, 'http://www.w3.org/2001/XMLSchema', 5)", + + "WITH" + + " s(how) AS (SELECT unnest('{4,5,7}'::int[]))," + + " r(isdoc) AS (" + + " SELECT" + + " javatest.lowlevelxmlecho(" + + " query_to_xml(" + + " 'SELECT ''hi'' AS textcol, 1 AS intcol', true, true, 'urn:testme'"+ + " ), how, params) IS DOCUMENT" + + " FROM" + + " s," + + " (SELECT 'schematest' AS schema) AS params" + + " )" + + "SELECT" + + " CASE WHEN every(isdoc)" + + " THEN javatest.logmessage('INFO', 'XML Schema tests succeeded')" + + " ELSE javatest.logmessage('WARNING'," + + " 'XML Schema tests had problems')" + + " END " + + "FROM" + + " r" + } +) + +@SQLAction(implementor="postgresql_xml", + requires={"prepareXMLTransform", "transformXML"}, + install={ "SELECT" + - " preparexmlschema('schematest', $$" + - "" + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - "" + - "$$, 'http://www.w3.org/2001/XMLSchema', 5)", - - "WITH" + - " s(how) AS (SELECT unnest('{4,5,7}'::int[]))," + - " r(isdoc) AS (" + - " SELECT" + - " javatest.lowlevelxmlecho(" + - " query_to_xml(" + - " 'SELECT ''hi'' AS textcol, 1 AS intcol', true, true, 'urn:testme'"+ - " ), how, params) IS DOCUMENT" + - " FROM" + - " s," + - " (SELECT 'schematest' AS schema) AS params" + - " )" + + " javatest.prepareXMLTransform('distinctElementNames'," + + "'" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "', 5, true)", + "SELECT" + - " CASE WHEN every(isdoc)" + - " THEN javatest.logmessage('INFO', 'XML Schema tests succeeded')" + - " ELSE javatest.logmessage('WARNING'," + - " 'XML Schema tests had problems')" + - " END " + - "FROM" + - " r" - } - ), - - @SQLAction(implementor="postgresql_xml", - requires={"prepareXMLTransform", "transformXML"}, - install={ - "SELECT" + - " javatest.prepareXMLTransform('distinctElementNames'," + - "'" + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - " " + - "', 5, true)", - - "SELECT" + - " CASE WHEN" + - " javatest.transformXML('distinctElementNames'," + - " '', 5, 5)::text" + - " =" + - " 'abcde'"+ - " THEN javatest.logmessage('INFO', 'XSLT 1.0 test succeeded')" + - " ELSE javatest.logmessage('WARNING', 'XSLT 1.0 test failed')" + - " END" - } - ) -}) + " CASE WHEN" + + " javatest.transformXML('distinctElementNames'," + + " '', 5, 5)::text" + + " =" + + " 'abcde'"+ + " THEN javatest.logmessage('INFO', 'XSLT 1.0 test succeeded')" + + " ELSE javatest.logmessage('WARNING', 'XSLT 1.0 test failed')" + + " END" + } +) @MappedUDT(schema="javatest", name="onexml", structure="c1 xml", implementor="postgresql_xml", comment="A composite type mapped by the PassXML example class") diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PreJSR310.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PreJSR310.java index b19f5ce43..c0409c0d7 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PreJSR310.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PreJSR310.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018- Tada AB and other contributors, as listed below. + * Copyright (c) 2018-2020 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 @@ -24,7 +24,6 @@ import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; /** * Some tests of pre-JSR 310 date/time/timestamp conversions. @@ -34,17 +33,15 @@ * This example relies on {@code implementor} tags reflecting the PostgreSQL * version, set up in the {@link ConditionalDDR} example. */ -@SQLActions({ - @SQLAction(provides="language java_tzset", install={ - "SELECT sqlj.alias_java_language('java_tzset', true)" - }, remove={ - "DROP LANGUAGE java_tzset" - }), +@SQLAction(provides="language java_tzset", install={ + "SELECT sqlj.alias_java_language('java_tzset', true)" +}, remove={ + "DROP LANGUAGE java_tzset" +}) - @SQLAction(implementor="postgresql_ge_90300", // needs LATERAL - requires="issue199", install={ - "SELECT javatest.issue199()" - }) +@SQLAction(implementor="postgresql_ge_90300", // needs LATERAL + requires="issue199", install={ + "SELECT javatest.issue199()" }) public class PreJSR310 { diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/RecordParameterDefaults.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/RecordParameterDefaults.java index d4f240cee..09d3dbbe8 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/RecordParameterDefaults.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/RecordParameterDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018- Tada AB and other contributors, as listed below. + * Copyright (c) 2018-2020 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 @@ -20,7 +20,6 @@ import org.postgresql.pljava.ResultSetProvider; import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.annotation.SQLType; /** @@ -33,19 +32,17 @@ * This example relies on {@code implementor} tags reflecting the PostgreSQL * version, set up in the {@link ConditionalDDR} example. */ -@SQLActions({ - @SQLAction( - provides = "paramtypeinfo type", // created in Triggers.java - install = { - "CREATE TYPE javatest.paramtypeinfo AS (" + - " name text, pgtypename text, javaclass text, tostring text" + - ")" - }, - remove = { - "DROP TYPE javatest.paramtypeinfo" - } - ) -}) +@SQLAction( + provides = "paramtypeinfo type", // created in Triggers.java + install = { + "CREATE TYPE javatest.paramtypeinfo AS (" + + " name text, pgtypename text, javaclass text, tostring text" + + ")" + }, + remove = { + "DROP TYPE javatest.paramtypeinfo" + } +) public class RecordParameterDefaults implements ResultSetProvider { /** diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java index f2606c912..dca34e5c7 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java @@ -31,37 +31,34 @@ import org.postgresql.pljava.annotation.Function; import static org.postgresql.pljava.annotation.Function.Effects.*; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; /** * Some methods used for testing the SPI JDBC driver. * * @author Thomas Hallgren */ -@SQLActions({ - @SQLAction(provides = "employees tables", install = { - "CREATE TABLE javatest.employees1" + - " (" + - " id int PRIMARY KEY," + - " name varchar(200)," + - " salary int" + - " )", - - "CREATE TABLE javatest.employees2" + - " (" + - " id int PRIMARY KEY," + - " name varchar(200)," + - " salary int," + - " transferDay date," + - " transferTime time" + - " )" - }, remove = { - "DROP TABLE javatest.employees2", - "DROP TABLE javatest.employees1" - } - ), - @SQLAction(requires = "issue228", install = "SELECT javatest.issue228()") -}) +@SQLAction(provides = "employees tables", install = { + "CREATE TABLE javatest.employees1" + + " (" + + " id int PRIMARY KEY," + + " name varchar(200)," + + " salary int" + + " )", + + "CREATE TABLE javatest.employees2" + + " (" + + " id int PRIMARY KEY," + + " name varchar(200)," + + " salary int," + + " transferDay date," + + " transferTime time" + + " )" + }, remove = { + "DROP TABLE javatest.employees2", + "DROP TABLE javatest.employees1" +} +) +@SQLAction(requires = "issue228", install = "SELECT javatest.issue228()") public class SPIActions { private static final String SP_CHECKSTATE = "sp.checkState"; diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Triggers.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Triggers.java index f950a602c..804ef9d83 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Triggers.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Triggers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2013 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2020 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 @@ -9,6 +9,7 @@ * Contributors: * Tada AB * Purdue University + * Chapman Flack */ package org.postgresql.pljava.example.annotation; @@ -22,7 +23,6 @@ import org.postgresql.pljava.TriggerData; import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.annotation.Trigger; import static org.postgresql.pljava.annotation.Trigger.Called.*; import static org.postgresql.pljava.annotation.Trigger.Constraint.*; @@ -41,41 +41,39 @@ * version, set up in the {@link ConditionalDDR} example. Constraint triggers * appear in PG 9.1, transition tables in PG 10. */ -@SQLActions({ - @SQLAction( - provides = "foobar tables", - install = { - "CREATE TABLE javatest.foobar_1 ( username text, stuff text )", - "CREATE TABLE javatest.foobar_2 ( username text, value numeric )" - }, - remove = { - "DROP TABLE javatest.foobar_2", - "DROP TABLE javatest.foobar_1" - } - ), - @SQLAction( - requires = "constraint triggers", - install = "INSERT INTO javatest.foobar_2(value) VALUES (45)" - ), - @SQLAction( - requires = "foobar triggers", - provides = "foobar2_42", - install = "INSERT INTO javatest.foobar_2(value) VALUES (42)" - ), - @SQLAction( - requires = { "transition triggers", "foobar2_42" }, - install = "UPDATE javatest.foobar_2 SET value = 43 WHERE value = 42" - ) - /* - * Note for another day: this would seem an excellent place to add a - * regression test for github issue #134 (make sure invocations of a - * trigger do not fail with SPI_ERROR_UNCONNECTED). However, any test - * here that runs from the deployment descriptor will be running when - * SPI is already connected, so a regression would not be caught. - * A proper test for it will have to wait for a proper testing harness - * invoking tests from outside PL/Java itself. - */ -}) +@SQLAction( + provides = "foobar tables", + install = { + "CREATE TABLE javatest.foobar_1 ( username text, stuff text )", + "CREATE TABLE javatest.foobar_2 ( username text, value numeric )" + }, + remove = { + "DROP TABLE javatest.foobar_2", + "DROP TABLE javatest.foobar_1" + } +) +@SQLAction( + requires = "constraint triggers", + install = "INSERT INTO javatest.foobar_2(value) VALUES (45)" +) +@SQLAction( + requires = "foobar triggers", + provides = "foobar2_42", + install = "INSERT INTO javatest.foobar_2(value) VALUES (42)" +) +@SQLAction( + requires = { "transition triggers", "foobar2_42" }, + install = "UPDATE javatest.foobar_2 SET value = 43 WHERE value = 42" +) +/* + * Note for another day: this would seem an excellent place to add a + * regression test for github issue #134 (make sure invocations of a + * trigger do not fail with SPI_ERROR_UNCONNECTED). However, any test + * here that runs from the deployment descriptor will be running when + * SPI is already connected, so a regression would not be caught. + * A proper test for it will have to wait for a proper testing harness + * invoking tests from outside PL/Java itself. + */ public class Triggers { /** diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/UnicodeRoundTripTest.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/UnicodeRoundTripTest.java index 568d64017..4f2c0ec47 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/UnicodeRoundTripTest.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/UnicodeRoundTripTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Tada AB and other contributors, as listed below. + * Copyright (c) 2015-2020 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 @@ -15,7 +15,6 @@ import java.sql.SQLException; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.annotation.Function; /** @@ -39,18 +38,17 @@ * This example relies on {@code implementor} tags reflecting the PostgreSQL * version, set up in the {@link ConditionalDDR} example, and also sets its own. */ -@SQLActions({ - @SQLAction(provides="postgresql_unicodetest", - implementor="postgresql_ge_90000", install= - "SELECT CASE" + - " WHEN 'UTF8' = current_setting('server_encoding')" + - " THEN set_config('pljava.implementors', 'postgresql_unicodetest,' ||" + - " current_setting('pljava.implementors'), true) " + - "END" - ), - @SQLAction(requires="unicodetest fn", - implementor="postgresql_unicodetest", - install= +@SQLAction(provides="postgresql_unicodetest", + implementor="postgresql_ge_90000", install= + "SELECT CASE" + + " WHEN 'UTF8' = current_setting('server_encoding')" + + " THEN set_config('pljava.implementors', 'postgresql_unicodetest,' ||" + + " current_setting('pljava.implementors'), true) " + + "END" +) +@SQLAction(requires="unicodetest fn", +implementor="postgresql_unicodetest", +install= " with " + " usable_codepoints ( cp ) as ( " + " select generate_series(1,x'd7ff'::int) " + @@ -88,15 +86,14 @@ " 'all Unicode codepoint ranges roundtripped successfully.') " + " end " + " from test_summary" - ), - @SQLAction( - install= - "CREATE TYPE unicodetestrow AS " + - "(matched boolean, cparray integer[], s text)", - remove="DROP TYPE unicodetestrow", - provides="unicodetestrow type" - ) -}) +) +@SQLAction( + install= + "CREATE TYPE unicodetestrow AS " + + "(matched boolean, cparray integer[], s text)", + remove="DROP TYPE unicodetestrow", + provides="unicodetestrow type" +) public class UnicodeRoundTripTest { /** * This function takes a string and an array of ints constructed in PG, diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/XMLRenderedTypes.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/XMLRenderedTypes.java index ca7f501d9..871e96445 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/XMLRenderedTypes.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/XMLRenderedTypes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Tada AB and other contributors, as listed below. + * Copyright (c) 2019-2020 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 @@ -17,7 +17,6 @@ import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; -import org.postgresql.pljava.annotation.SQLActions; import org.postgresql.pljava.annotation.SQLType; import static org.postgresql.pljava.example.LoggerTest.logMessage; @@ -30,16 +29,14 @@ * in case of being loaded into a PostgreSQL instance built without that type. * The {@code pg_node_tree} type appears in 9.1. */ -@SQLActions({ - @SQLAction(implementor="postgresql_ge_90100", - provides="postgresql_xml_ge91", - install= - "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + - " THEN set_config('pljava.implementors', 'postgresql_xml_ge91,' || " + - " current_setting('pljava.implementors'), true) " + - "END" - ) -}) +@SQLAction(implementor="postgresql_ge_90100", + provides="postgresql_xml_ge91", + install= + "SELECT CASE (SELECT 1 FROM pg_type WHERE typname = 'xml') WHEN 1" + + " THEN set_config('pljava.implementors', 'postgresql_xml_ge91,' || " + + " current_setting('pljava.implementors'), true) " + + "END" +) public class XMLRenderedTypes { @Function(schema="javatest", implementor="postgresql_xml_ge91")