001/* 002 * Copyright (c) 2020-2022 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 java.sql.ResultSet; 015import java.sql.ResultSetMetaData; 016import java.sql.SQLException; 017 018import java.util.Iterator; 019import java.util.List; 020 021import org.postgresql.pljava.ResultSetProvider; 022import org.postgresql.pljava.annotation.Function; 023import org.postgresql.pljava.annotation.SQLAction; 024import org.postgresql.pljava.annotation.SQLType; 025 026/** 027 * Demonstrates {@code @Function(out={...})} for a function that returns a 028 * non-predeclared composite type. 029 */ 030@SQLAction(requires = { "helloOutParams", "helloTable" }, install = { 031 "SELECT" + 032 " CASE WHEN want IS NOT DISTINCT FROM helloOutParams()" + 033 " THEN javatest.logmessage('INFO', 'composite return passes')" + 034 " ELSE javatest.logmessage('WARNING', 'composite return fails')" + 035 " END" + 036 " FROM" + 037 " (SELECT 'Hello' ::text, 'world' ::text) AS want", 038 039 "WITH" + 040 " expected AS (VALUES" + 041 " ('Hello' ::text, 'twelve' ::text)," + 042 " ('Hello', 'thirteen')," + 043 " ('Hello', 'love')" + 044 " )" + 045 "SELECT" + 046 " CASE WHEN every(want IS NOT DISTINCT FROM got)" + 047 " THEN javatest.logmessage('INFO', 'set of composite return passes')" + 048 " ELSE javatest.logmessage('WARNING', 'set of composite return fails')" + 049 " END" + 050 " FROM" + 051 " (SELECT row_number() OVER (), * FROM expected) AS want" + 052 " LEFT JOIN (SELECT row_number() OVER (), * FROM hellotable()) AS got" + 053 " USING (row_number)" 054}) 055public class ReturnComposite implements ResultSetProvider.Large 056{ 057 /** 058 * Returns a two-column composite result that does not have to be 059 * a predeclared composite type, or require the calling SQL query to 060 * follow the function call with a result column definition list, as is 061 * needed for a bare {@code RECORD} return type. 062 */ 063 @Function( 064 schema = "javatest", out = { "greeting text", "addressee text" }, 065 provides = "helloOutParams" 066 ) 067 public static boolean helloOutParams(ResultSet out) throws SQLException 068 { 069 out.updateString(1, "Hello"); 070 out.updateString(2, "world"); 071 return true; 072 } 073 074 /** 075 * A function that does not return a composite type, despite having 076 * a similar Java form. 077 *<p> 078 * Without the {@code type=} element, this would not be mistaken for 079 * composite. With the {@code type=} element (a contrived example, will cast 080 * the method's boolean result to text), PL/Java would normally match the 081 * method to the composite pattern (other than {@code pg_catalog.RECORD}, 082 * PL/Java does not pretend to know at compile time which types might be 083 * composite). The explicit {@code SQLType} annotation on the trailing 084 * {@code ResultSet} parameter forces it to be seen as an input, and the 085 * method to be seen as an ordinary method that happens to return boolean. 086 */ 087 @Function( 088 schema = "javatest", type = "text" 089 ) 090 public static boolean 091 notOutParams(@SQLType("pg_catalog.record") ResultSet in) 092 throws SQLException 093 { 094 return true; 095 } 096 097 /** 098 * Returns a two-column table result that does not have to be 099 * a predeclared composite type, or require the calling SQL query to 100 * follow the function call with a result column definition list, as is 101 * needed for a bare {@code RECORD} return type. 102 */ 103 @Function( 104 schema = "javatest", out = { "greeting text", "addressee text" }, 105 provides = "helloTable" 106 ) 107 public static ResultSetProvider helloTable() 108 throws SQLException 109 { 110 return new ReturnComposite(); 111 } 112 113 Iterator<String> addressees = 114 List.of("twelve", "thirteen", "love").iterator(); 115 116 @Override 117 public boolean assignRowValues(ResultSet out, long currentRow) 118 throws SQLException 119 { 120 if ( ! addressees.hasNext() ) 121 return false; 122 123 out.updateString(1, "Hello"); 124 out.updateString(2, addressees.next()); 125 return true; 126 } 127 128 @Override 129 public void close() 130 { 131 } 132 133 /** 134 * Returns a result described by <em>one</em> {@code out} parameter. 135 *<p> 136 * Such a method is written in the style of any method that returns 137 * a scalar value, rather than receiving a writable {@code ResultSet} 138 * as a parameter. 139 */ 140 @Function( 141 schema = "javatest", out = { "greeting text" } 142 ) 143 public static String helloOneOut() throws SQLException 144 { 145 return "Hello"; 146 } 147 148 /** 149 * Has a boolean result described by <em>one</em> {@code out} parameter. 150 *<p> 151 * Because this method returns boolean and has a trailing row-typed 152 * <em>input</em> parameter, that parameter must have an {@code SQLType} 153 * annotation so that the method will not look like the more-than-one-OUT 154 * composite form, which would be rejected as a likely mistake. 155 */ 156 @Function( 157 schema = "javatest", out = { "exquisite boolean" } 158 ) 159 public static boolean boolOneOut(@SQLType("pg_catalog.record") ResultSet in) 160 throws SQLException 161 { 162 return true; 163 } 164 165 /** 166 * Returns a table result described by <em>one</em> {@code out} parameter. 167 *<p> 168 * Such a method is written in the style of any method that returns a set 169 * of some scalar value, using an {@code Iterator} rather than a 170 * {@code ResultSetProvider} or {@code ResultSetHandle}. 171 */ 172 @Function( 173 schema = "javatest", out = { "addressee text" } 174 ) 175 public static Iterator<String> helloOneOutTable() throws SQLException 176 { 177 return new ReturnComposite().addressees; 178 } 179}