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}