/*
* Copyright (c) 2004-2016 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
* which accompanies this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*
* Contributors:
* Tada AB
* Chapman Flack
*/
package org.postgresql.pljava.example.annotation;
import java.sql.SQLData;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
import org.postgresql.pljava.annotation.Function;
import org.postgresql.pljava.annotation.SQLAction;
import org.postgresql.pljava.annotation.SQLType;
import org.postgresql.pljava.annotation.BaseUDT;
import static org.postgresql.pljava.annotation.Function.Effects.IMMUTABLE;
import static
org.postgresql.pljava.annotation.Function.OnNullInput.RETURNS_NULL;
/**
* An integer-like data type accepting a type modifier: IntWithMod(even) or
* IntWithMod(odd).
*<p>
* Support for type modifiers in PL/Java is only partial so far. It does not
* yet honor typmods passed to the input/receive functions ... but it may be
* that only COPY operations require that. Most uses of types with modifiers
* seem to pass -1 when first constructing the value, then use a typmod-
* application cast, and all of that can be done in PL/Java already.
*<p>
* Serving suggestion:
*<pre>
* CREATE TABLE evod (
* ev javatest.IntWithMod(even),
* od javatest.IntWithMod(odd)
* );
* INSERT INTO evod ( ev, od ) VALUES ( '4', '7' );
*</pre>
*<p>
* Of course this example more or less duplicates what you could do in two lines
* with CREATE DOMAIN. But it is enough to illustrate the process.
*<p>
* Certainly, it would be less tedious with some more annotation support and
* autogeneration of the ordering dependencies that are now added by hand here.
*/
@SQLAction(requires={"IntWithMod type", "IntWithMod modApply"},
remove="DROP CAST (javatest.IntWithMod AS javatest.IntWithMod)",
install={
"CREATE CAST (javatest.IntWithMod AS javatest.IntWithMod)" +
" WITH FUNCTION javatest.intwithmod_typmodapply(" +
" javatest.IntWithMod, integer, boolean)",
"COMMENT ON CAST (javatest.IntWithMod AS javatest.IntWithMod) IS '" +
"Cast that applies/verifies the type modifier on an IntWithMod.'"
}
)
@BaseUDT(schema="javatest", provides="IntWithMod type",
requires={"IntWithMod modIn", "IntWithMod modOut"},
typeModifierInput="javatest.intwithmod_typmodin",
typeModifierOutput="javatest.intwithmod_typmodout",
like="pg_catalog.int4")
public class IntWithMod implements SQLData {
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
public static IntWithMod parse(String input, String typeName)
throws SQLException {
try {
int v = Integer.parseInt(input);
IntWithMod o = new IntWithMod();
o.m_value = v;
o.m_typeName = typeName;
return o;
}
catch ( NumberFormatException e ) {
throw new SQLDataException("invalid IntWithMod value", "22P02", e);
}
}
private int m_value;
private String m_typeName;
public IntWithMod() {
}
@Override
public String getSQLTypeName() {
return m_typeName;
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
m_value = stream.readInt();
m_typeName = typeName;
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public String toString() {
return String.valueOf(m_value);
}
@Function(effects=IMMUTABLE, onNullInput=RETURNS_NULL)
@Override
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeInt(m_value);
}
/**
* Type modifier input function for IntWithMod type: accepts
* "even" or "odd". The modifier value is 0 for even or 1 for odd.
*/
@Function(schema="javatest", name="intwithmod_typmodin",
provides="IntWithMod modIn",
effects=IMMUTABLE, onNullInput=RETURNS_NULL)
public static int modIn(@SQLType("cstring[]") String[] toks)
throws SQLException {
if ( 1 != toks.length )
throw new SQLDataException(
"only one type modifier allowed for IntWithMod", "22023");
if ( "even".equalsIgnoreCase(toks[0]) )
return 0;
if ( "odd".equalsIgnoreCase(toks[0]) )
return 1;
throw new SQLDataException(
"modifier for IntWithMod must be \"even\" or \"odd\"", "22023");
}
/**
* Type modifier output function for IntWithMod type.
*/
@Function(schema="javatest", name="intwithmod_typmodout",
provides="IntWithMod modOut", type="cstring",
effects=IMMUTABLE, onNullInput=RETURNS_NULL)
public static String modOut(int mod) throws SQLException {
switch ( mod ) {
case 0: return "(even)";
case 1: return "(odd)";
default:
throw new SQLException("impossible IntWithMod typmod: " + mod);
}
}
/**
* Function backing the type-modifier application cast for IntWithMod type.
*/
@Function(schema="javatest", name="intwithmod_typmodapply",
requires="IntWithMod type", provides="IntWithMod modApply",
type="javatest.IntWithMod", effects=IMMUTABLE, onNullInput=RETURNS_NULL)
public static IntWithMod modApply(
@SQLType("javatest.IntWithMod") IntWithMod iwm,
int mod, boolean explicit) throws SQLException {
if ( -1 == mod )
return iwm;
if ( (iwm.m_value & 1) != mod )
throw new SQLDataException(
"invalid value " + iwm + " for " +
iwm.getSQLTypeName() + modOut(mod), "22000");
iwm.m_typeName += modOut(mod);
return iwm;
}
}