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}