001/*
002 * Copyright (c) 2004-2021 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 *   Tada AB
011 *   Chapman Flack
012 */
013package org.postgresql.pljava.example.annotation;
014
015import java.io.IOException;
016import java.io.StreamTokenizer;
017import java.io.StringReader;
018
019import static java.lang.Math.hypot;
020
021import java.sql.SQLData;
022import java.sql.SQLException;
023import java.sql.SQLInput;
024import java.sql.SQLOutput;
025
026import java.util.logging.Logger;
027
028import org.postgresql.pljava.annotation.Aggregate;
029import org.postgresql.pljava.annotation.Function;
030import org.postgresql.pljava.annotation.Operator;
031import static org.postgresql.pljava.annotation.Operator.SELF;
032import static org.postgresql.pljava.annotation.Operator.TWIN;
033import org.postgresql.pljava.annotation.SQLAction;
034import org.postgresql.pljava.annotation.BaseUDT;
035
036import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
037import static
038    org.postgresql.pljava.annotation.Function.OnNullInput.RETURNS_NULL;
039
040/**
041 * Complex (re and im parts are doubles) implemented in Java as a scalar UDT.
042 *<p>
043 * The {@code SQLAction} here demonstrates a {@code requires} tag
044 * ("complex relationals"} that has multiple providers, something not allowed
045 * prior to PL/Java 1.6.1. It is more succinct to require one tag and have each
046 * of the relational operators 'provide' it than to have to define and require
047 * several different tags to accomplish the same thing.
048 *<p>
049 * The operator class created here is not actively used for anything (the
050 * examples will not break if it is removed), but the {@code minMagnitude}
051 * example aggregate does specify a {@code sortOperator}, which PostgreSQL will
052 * not exploit in query optimization without finding it as a member of
053 * a {@code btree} operator class.
054 *<p>
055 * Note that {@code CREATE OPERATOR CLASS} implicitly creates an operator family
056 * as well (unless one is explicitly specified), so the correct {@code remove}
057 * action to clean everything up is {@code DROP OPERATOR FAMILY} (which takes
058 * care of dropping the class).
059 */
060@SQLAction(requires = { "complex assertHasValues", "complex relationals" },
061    install = {
062        "CREATE OPERATOR CLASS javatest.complex_ops" +
063        "  DEFAULT FOR TYPE javatest.complex USING btree" +
064        " AS" +
065        "  OPERATOR 1 javatest.<  ," +
066        "  OPERATOR 2 javatest.<= ," +
067        "  OPERATOR 3 javatest.=  ," +
068        "  OPERATOR 4 javatest.>= ," +
069        "  OPERATOR 5 javatest.>  ," +
070        "  FUNCTION 1 javatest.cmpMagnitude(javatest.complex,javatest.complex)",
071
072        "SELECT javatest.assertHasValues(" +
073        " CAST('(1,2)' AS javatest.complex), 1, 2)",
074
075        "SELECT javatest.assertHasValues(" +
076        " 2.0 + CAST('(1,2)' AS javatest.complex) + 3.0, 6, 2)",
077
078        "SELECT" +
079        " CASE WHEN" +
080        "  '(1,2)'::javatest.complex < '(2,2)'::javatest.complex" +
081        " AND" +
082        "  '(2,2)'::javatest.complex > '(1,2)'::javatest.complex" +
083        " AND" +
084        "  '(1,2)'::javatest.complex <= '(2,2)'::javatest.complex" +
085        " THEN javatest.logmessage('INFO', 'ComplexScalar operators ok')" +
086        " ELSE javatest.logmessage('WARNING', 'ComplexScalar operators ng')" +
087        " END"
088    },
089
090    remove = {
091        "DROP OPERATOR FAMILY javatest.complex_ops USING btree"
092    }
093)
094@BaseUDT(schema="javatest", name="complex",
095    internalLength=16, alignment=BaseUDT.Alignment.DOUBLE)
096public class ComplexScalar implements SQLData {
097    private static Logger s_logger = Logger.getAnonymousLogger();
098
099    /**
100     * Return the same 'complex' passed in, logging its contents at level INFO.
101     *<p>
102     * Also create an unnecessary {@code <<} operator for this, with an equally
103     * unnecessary explicit operand type, simply as a regression test
104     * of issue #330.
105     * @param cpl any instance of this UDT
106     * @return the same instance passed in
107     */
108    @Operator(
109        name = "javatest.<<", right = "javatest.complex"
110    )
111    @Function(
112        schema="javatest", name="logcomplex", effects=IMMUTABLE,
113        onNullInput=RETURNS_NULL)
114    public static ComplexScalar logAndReturn(ComplexScalar cpl) {
115        s_logger.info(cpl.getSQLTypeName() + cpl);
116        return cpl;
117    }
118
119    /**
120     * Assert a 'complex' has given re and im values, to test that its
121     * representation in Java corresponds to what PostgreSQL sees.
122     * @param cpl an instance of this UDT
123     * @param re the 'real' value it should have
124     * @param im the 'imaginary' value it should have
125     * @throws SQLException if the values do not match
126     */
127    @Function(schema="javatest",
128        provides="complex assertHasValues",
129        effects=IMMUTABLE, onNullInput=RETURNS_NULL)
130    public static void assertHasValues(ComplexScalar cpl, double re, double im)
131        throws SQLException
132    {
133        if ( cpl.m_x != re  ||  cpl.m_y != im )
134            throw new SQLException("assertHasValues fails");
135    }
136
137    @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
138    public static ComplexScalar parse(String input, String typeName)
139            throws SQLException {
140        try {
141            StreamTokenizer tz = new StreamTokenizer(new StringReader(input));
142            if (tz.nextToken() == '('
143                    && tz.nextToken() == StreamTokenizer.TT_NUMBER) {
144                double x = tz.nval;
145                if (tz.nextToken() == ','
146                        && tz.nextToken() == StreamTokenizer.TT_NUMBER) {
147                    double y = tz.nval;
148                    if (tz.nextToken() == ')') {
149                        s_logger.fine(typeName + " from string");
150                        return new ComplexScalar(x, y, typeName);
151                    }
152                }
153            }
154            throw new SQLException("Unable to parse complex from string \""
155                    + input + '"');
156        } catch (IOException e) {
157            throw new SQLException(e.getMessage());
158        }
159    }
160
161    private double m_x;
162
163    private double m_y;
164
165    private String m_typeName;
166
167    public ComplexScalar() {
168    }
169
170    /**
171     * Add two instances of {@code ComplexScalar}.
172     */
173    @Operator(name = {"javatest","+"}, commutator = SELF)
174    @Function(
175        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL
176    )
177    public static ComplexScalar add(ComplexScalar a, ComplexScalar b)
178    {
179        return new ComplexScalar(
180            a.m_x + b.m_x, a.m_y + b.m_y, a.m_typeName);
181    }
182
183    /**
184     * Add a {@code ComplexScalar} and a real (supplied as a {@code double}).
185     */
186    @Operator(name = {"javatest","+"}, commutator = TWIN)
187    @Operator(name = {"javatest","+"},  synthetic = TWIN)
188    @Function(
189        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL
190    )
191    public static ComplexScalar add(ComplexScalar a, double b)
192    {
193        return new ComplexScalar(a.m_x + b, a.m_y, a.m_typeName);
194    }
195
196    /**
197     * True if the left argument is smaller than the right in magnitude
198     * (Euclidean distance from the origin).
199     */
200    @Operator(
201        name = "javatest.<",
202        commutator = "javatest.>", negator = "javatest.>=",
203        provides = "complex relationals"
204    )
205    @Operator(
206        name = "javatest.<=", synthetic = "javatest.magnitudeLE",
207        provides = "complex relationals"
208    )
209    @Operator(
210        name = "javatest.>=", synthetic = "javatest.magnitudeGE",
211        commutator = "javatest.<=", provides = "complex relationals"
212    )
213    @Operator(
214        name = "javatest.>", synthetic = "javatest.magnitudeGT",
215        negator = "javatest.<=", provides = "complex relationals"
216    )
217    @Function(
218        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL
219    )
220    public static boolean magnitudeLT(ComplexScalar a, ComplexScalar b)
221    {
222        return hypot(a.m_x, a.m_y) < hypot(b.m_x, b.m_y);
223    }
224
225    /**
226     * True if the left argument and the right are componentwise equal.
227     */
228    @Operator(
229        name = "javatest.=",
230        commutator = SELF, negator = "javatest.<>",
231        provides = "complex relationals"
232    )
233    @Operator(
234        name = "javatest.<>", synthetic = "javatest.componentsNE",
235        commutator = SELF, provides = "complex relationals"
236    )
237    @Function(
238        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL
239    )
240    public static boolean componentsEQ(ComplexScalar a, ComplexScalar b)
241    {
242        return a.m_x == b.m_x  &&  a.m_y == b.m_y;
243    }
244
245    /**
246     * True if the complex argument is real-valued and equal to the real
247     * argument.
248     *<p>
249     * From one equality method on (complex,double) can be synthesized all four
250     * cross-type operators, {@code =} and {@code <>} for that pair of types and
251     * their {@code TWIN} commutators. One of the {@code <>} twins does need to
252     * specify what its synthetic function should be named.
253     */
254    @Operator(
255        name = "javatest.=",
256        commutator = TWIN, negator = "javatest.<>",
257        provides = "complex:double relationals"
258    )
259    @Operator(
260        name = "javatest.=",
261        synthetic = TWIN, negator = "javatest.<>",
262        provides = "complex:double relationals"
263    )
264    @Operator(
265        name = "javatest.<>", synthetic = "javatest.neToReal",
266        commutator = TWIN, provides = "complex:double relationals"
267    )
268    @Operator(
269        name = "javatest.<>", synthetic = TWIN,
270        provides = "complex:double relationals"
271    )
272    @Function(
273        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL
274    )
275    public static boolean eqToReal(ComplexScalar a, double b)
276    {
277        return a.m_x == b  &&  0. == a.m_y;
278    }
279
280    /**
281     * As an ordinary function, returns the lesser in magnitude of two
282     * arguments; as a simple aggregate, returns the least in magnitude over its
283     * aggregated arguments.
284     *<p>
285     * As an aggregate, this is a simple example where this method serves as the
286     * {@code accumulate} function, the state (<em>a</em> here) has the same
287     * type as the argument (here <em>b</em>), there is no {@code finish}
288     * function, and the final value of the state is the result.
289     *<p>
290     * An optimization is available in case there is an index on the aggregated
291     * values based on the {@code <} operator above; in that case, the first
292     * value found in a scan of that index is the aggregate result. That is
293     * indicated here by naming the {@code <} operator as {@code sortOperator}.
294     */
295    @Aggregate(sortOperator = "javatest.<")
296    @Function(
297        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL
298    )
299    public static ComplexScalar minMagnitude(ComplexScalar a, ComplexScalar b)
300    {
301        return magnitudeLT(a, b) ? a : b;
302    }
303
304    /**
305     * An integer-returning comparison function by complex magnitude, usable to
306     * complete an example {@code btree} operator class.
307     */
308    @Function(
309        schema = "javatest", effects = IMMUTABLE, onNullInput = RETURNS_NULL,
310        provides = "complex relationals"
311    )
312    public static int cmpMagnitude(ComplexScalar a, ComplexScalar b)
313    {
314        if ( magnitudeLT(a, b) )
315            return -1;
316        if ( magnitudeLT(b, a) )
317            return 1;
318        return 0;
319    }
320
321    public ComplexScalar(double x, double y, String typeName) {
322        m_x = x;
323        m_y = y;
324        m_typeName = typeName;
325    }
326
327    @Override
328    public String getSQLTypeName() {
329        return m_typeName;
330    }
331
332    @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
333    @Override
334    public void readSQL(SQLInput stream, String typeName) throws SQLException {
335        s_logger.fine(typeName + " from SQLInput");
336        m_x = stream.readDouble();
337        m_y = stream.readDouble();
338        m_typeName = typeName;
339    }
340
341    @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
342    @Override
343    public String toString() {
344        s_logger.fine(m_typeName + " toString");
345        StringBuffer sb = new StringBuffer();
346        sb.append('(');
347        sb.append(m_x);
348        sb.append(',');
349        sb.append(m_y);
350        sb.append(')');
351        return sb.toString();
352    }
353
354    @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
355    @Override
356    public void writeSQL(SQLOutput stream) throws SQLException {
357        s_logger.fine(m_typeName + " to SQLOutput");
358        stream.writeDouble(m_x);
359        stream.writeDouble(m_y);
360    }
361}