001/*
002 * Copyright (c) 2020 Tada AB and other contributors, as listed below.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the The BSD 3-Clause License
006 * which accompanies this distribution, and is available at
007 * http://opensource.org/licenses/BSD-3-Clause
008 *
009 * Contributors:
010 *   Chapman Flack
011 */
012package org.postgresql.pljava.example.annotation;
013
014import static java.util.Arrays.stream;
015import java.util.Objects;
016
017import org.postgresql.pljava.annotation.Function;
018import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
019import static
020    org.postgresql.pljava.annotation.Function.OnNullInput.RETURNS_NULL;
021import org.postgresql.pljava.annotation.SQLAction;
022import org.postgresql.pljava.annotation.SQLType;
023
024/**
025 * Provides example methods to illustrate variadic functions.
026 *<p>
027 * The key is the {@code @Function} annotation declaring the function variadic
028 * to PostgreSQL. The Java method parameter is declared as an ordinary array,
029 * not with Java's {@code ...} syntax; in fact, that would be impossible for a
030 * function with a composite return type (where the Java signature would have to
031 * include a {@code ResultSet} parameter after the variadic input parameter).
032 */
033@SQLAction(
034    requires = { "sumOfSquares", "sumOfSquaresBoxed" },
035    install = {
036        /*
037         * In addition to the two sumOfSquares functions that are defined in
038         * this class using annotations, emit some direct SQL to declare a
039         * javaformat function that refers directly to java.lang.String.format,
040         * which is a function declared variadic in Java.
041         */
042        "CREATE FUNCTION javatest.javaformat(" +
043        "  format pg_catalog.text," +
044        "  VARIADIC args pg_catalog.anyarray" +
045        "  DEFAULT CAST(ARRAY[] AS pg_catalog.text[]))" +
046        " RETURNS pg_catalog.text" +
047        " RETURNS NULL ON NULL INPUT" +
048        " LANGUAGE java" +
049        " AS 'java.lang.String=" +
050        "     java.lang.String.format(java.lang.String,java.lang.Object[])'",
051
052        "COMMENT ON FUNCTION javatest.javaformat(" +
053        " pg_catalog.text, VARIADIC pg_catalog.anyarray) IS '" +
054        "Invoke Java''s String.format with a format string and any number of " +
055        "arguments. This is not quite as general as the Java method implies, " +
056        "because, while the variadic argument is declared ''anyarray'' and " +
057        "its members can have any type, PostgreSQL requires all of them to " +
058        "have the same type in any given call. Furthermore, in the VARIADIC " +
059        "anyarray case, as here, the actual arguments must not all have " +
060        "''unknown'' type; if supplying bare literals, one must be cast to " +
061        "a type. PostgreSQL will not recognize a call of a variadic function " +
062        "unless at least one argument to populate the variadic parameter is " +
063        "supplied; to allow calls that don''t pass any, give the variadic " +
064        "parameter an empty-array default, as done here.'",
065
066        /*
067         * Test a bunch of variadic calls.
068         */
069        "SELECT" +
070        "  CASE" +
071        "   WHEN s.ok AND d.ok" +
072        "   THEN javatest.logmessage('INFO', 'variadic calls ok')" +
073        "   ELSE javatest.logmessage('WARNING', 'variadic calls ng')" +
074        "  END" +
075        " FROM" +
076        "  (SELECT" +
077        "    pg_catalog.every(expect IS NOT DISTINCT FROM got)" +
078        "   FROM" +
079        "    (VALUES" +
080        "     (" +
081        "      'Hello, world'," +
082        "      javatest.javaformat('Hello, %s', 'world'::text)" +
083        "     )" +
084        "    ) AS t(expect, got)" +
085        "  ) AS s(ok)," +
086        "  (SELECT" +
087        "    pg_catalog.every(expect IS NOT DISTINCT FROM got)" +
088        "   FROM" +
089        "    (VALUES" +
090        "     (14.0, javatest.sumOfSquares(1, 2, 3))," +
091        "     (14.0, javatest.sumOfSquares(1, 2, null, 3))," +
092        "     ( 0.0, javatest.sumOfSquares())," +
093        "     (14.0, javatest.sumOfSquaresBoxed(1, 2, 3))," +
094        "     (null, javatest.sumOfSquaresBoxed(1, 2, null, 3))" +
095        "    ) AS t(expect, got)" +
096        "  ) AS d(ok)"
097    },
098
099    remove = "DROP FUNCTION javatest.javaformat(pg_catalog.text,anyarray)"
100)
101public class Variadic {
102    private Variadic() { } // do not instantiate
103
104    /**
105     * Compute a double-precision sum of squares, returning null if any input
106     * value is null.
107     *<p>
108     * The {@code RETURNS_NULL} annotation does not mean the array collecting
109     * the variadic arguments cannot have null entries; it only means PostgreSQL
110     * will never call this function with null for the array itself.
111     */
112    @Function(
113        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL,
114        variadic = true, provides = "sumOfSquaresBoxed"
115    )
116    public static Double sumOfSquaresBoxed(Double[] vals)
117    {
118        if ( stream(vals).anyMatch(Objects::isNull) )
119            return null;
120
121        return
122            stream(vals).mapToDouble(Double::doubleValue).map(v -> v*v).sum();
123    }
124
125    /**
126     * Compute a double-precision sum of squares, treating any null input
127     * as zero.
128     *<p>
129     * The {@code RETURNS_NULL} annotation does not mean the array collecting
130     * the variadic arguments cannot have null entries; it only means PostgreSQL
131     * will never call this function with null for the array itself. Because
132     * the Java parameter type here is primitive and cannot represent nulls,
133     * PL/Java will have silently replaced any nulls in the input with zeros.
134     *<p>
135     * This version also demonstrates using {@link SQLType @SQLType} to give
136     * the variadic parameter an empty-array default, so PostgreSQL will allow
137     * the function to be called with no corresponding arguments. Without that,
138     * PostgreSQL won't recognize a call to the function unless at least one
139     * argument corresponding to the variadic parameter is supplied.
140     */
141    @Function(
142        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL,
143        variadic = true, provides = "sumOfSquares"
144    )
145    public static double sumOfSquares(@SQLType(defaultValue={}) double[] vals)
146    {
147        return stream(vals).map(v -> v*v).sum();
148    }
149}