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}