001/* 002 * Copyright (c) 2004-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 * Tada AB 011 * Chapman Flack 012 */ 013package org.postgresql.pljava.example.annotation; 014 015import java.sql.Connection; 016import static java.sql.DriverManager.getConnection; 017import java.sql.SQLData; 018import java.sql.SQLDataException; 019import java.sql.SQLException; 020import java.sql.SQLInput; 021import java.sql.SQLOutput; 022import java.sql.Statement; 023 024import org.postgresql.pljava.annotation.Cast; 025import org.postgresql.pljava.annotation.Function; 026import org.postgresql.pljava.annotation.SQLAction; 027import org.postgresql.pljava.annotation.SQLType; 028import org.postgresql.pljava.annotation.BaseUDT; 029 030import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE; 031import static 032 org.postgresql.pljava.annotation.Function.OnNullInput.RETURNS_NULL; 033 034/** 035 * An integer-like data type accepting a type modifier: IntWithMod(even) or 036 * IntWithMod(odd). 037 *<p> 038 * Support for type modifiers in PL/Java is only partial so far. It does not 039 * yet honor typmods passed to the input/receive functions ... but it may be 040 * that only COPY operations require that. Most uses of types with modifiers 041 * seem to pass -1 when first constructing the value, then use a typmod- 042 * application cast, and all of that can be done in PL/Java already. 043 *<p> 044 * Serving suggestion: 045 *<pre> 046 * CREATE TABLE evod ( 047 * ev javatest.IntWithMod(even), 048 * od javatest.IntWithMod(odd) 049 * ); 050 * INSERT INTO evod ( ev, od ) VALUES ( '4', '7' ); 051 *</pre> 052 *<p> 053 * Of course this example more or less duplicates what you could do in two lines 054 * with CREATE DOMAIN. But it is enough to illustrate the process. 055 */ 056@SQLAction(requires="IntWithMod modCast", 057 install={ 058 "SELECT CAST('42' AS javatest.IntWithMod(even))" 059 } 060) 061@BaseUDT(schema="javatest", provides="IntWithMod type", 062 typeModifierInput="javatest.intwithmod_typmodin", 063 typeModifierOutput="javatest.intwithmod_typmodout", 064 like="pg_catalog.int4") 065public class IntWithMod implements SQLData { 066 067 @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL) 068 public static IntWithMod parse(String input, String typeName) 069 throws SQLException { 070 try { 071 int v = Integer.parseInt(input); 072 IntWithMod o = new IntWithMod(); 073 o.m_value = v; 074 o.m_typeName = typeName; 075 return o; 076 } 077 catch ( NumberFormatException e ) { 078 throw new SQLDataException("invalid IntWithMod value", "22P02", e); 079 } 080 } 081 082 private int m_value; 083 084 private String m_typeName; 085 086 public IntWithMod() { 087 } 088 089 @Override 090 public String getSQLTypeName() { 091 return m_typeName; 092 } 093 094 @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL) 095 @Override 096 public void readSQL(SQLInput stream, String typeName) throws SQLException { 097 m_value = stream.readInt(); 098 m_typeName = typeName; 099 100 /* 101 * This bit here is completely extraneous to the IntWithMod example, but 102 * simply included to verify that PL/Java works right if a UDT's readSQL 103 * method ends up invoking some other PL/Java function. 104 */ 105 try ( 106 Connection c = getConnection("jdbc:default:connection"); 107 Statement s = c.createStatement(); 108 ) 109 { 110 s.execute("SELECT javatest.java_addone(42)"); 111 } 112 } 113 114 @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL) 115 @Override 116 public String toString() { 117 return String.valueOf(m_value); 118 } 119 120 @Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL) 121 @Override 122 public void writeSQL(SQLOutput stream) throws SQLException { 123 stream.writeInt(m_value); 124 } 125 126 /** 127 * Type modifier input function for IntWithMod type: accepts 128 * "even" or "odd". The modifier value is 0 for even or 1 for odd. 129 */ 130 @Function(schema="javatest", name="intwithmod_typmodin", 131 effects=IMMUTABLE, onNullInput=RETURNS_NULL) 132 public static int modIn(@SQLType("pg_catalog.cstring[]") String[] toks) 133 throws SQLException { 134 if ( 1 != toks.length ) 135 throw new SQLDataException( 136 "only one type modifier allowed for IntWithMod", "22023"); 137 if ( "even".equalsIgnoreCase(toks[0]) ) 138 return 0; 139 if ( "odd".equalsIgnoreCase(toks[0]) ) 140 return 1; 141 throw new SQLDataException( 142 "modifier for IntWithMod must be \"even\" or \"odd\"", "22023"); 143 } 144 145 /** 146 * Type modifier output function for IntWithMod type. 147 */ 148 @Function(schema="javatest", name="intwithmod_typmodout", 149 type="pg_catalog.cstring", 150 effects=IMMUTABLE, onNullInput=RETURNS_NULL) 151 public static String modOut(int mod) throws SQLException { 152 switch ( mod ) { 153 case 0: return "(even)"; 154 case 1: return "(odd)"; 155 default: 156 throw new SQLException("impossible IntWithMod typmod: " + mod); 157 } 158 } 159 160 /** 161 * Function backing the type-modifier application cast for IntWithMod type. 162 */ 163 @Function(schema="javatest", name="intwithmod_typmodapply", 164 effects=IMMUTABLE, onNullInput=RETURNS_NULL) 165 @Cast(comment= 166 "Cast that applies/verifies the type modifier on an IntWithMod.", 167 provides="IntWithMod modCast") 168 public static IntWithMod modApply(IntWithMod iwm, int mod, boolean explicit) 169 throws SQLException 170 { 171 if ( -1 == mod ) 172 return iwm; 173 if ( (iwm.m_value & 1) != mod ) 174 throw new SQLDataException( 175 "invalid value " + iwm + " for " + 176 iwm.getSQLTypeName() + modOut(mod), "22000"); 177 iwm.m_typeName += modOut(mod); 178 return iwm; 179 } 180}