Enum converter with postgres array type

605 views
Skip to first unread message

Emrul Islam

unread,
Jun 17, 2016, 9:25:44 PM6/17/16
to jOOQ User Group
Hi,

I am using Postgres and have the following schema:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

CREATE TABLE test_enum_array
(
  "eArray" mood[]
)

In my Jooq codegen config I have the following:

<customTypes>
<customType>
<name>Mood</name>
<type>org.test.enums.Mood</type>
<converter>org.test.db.util.MoodConverter</converter>
</customType>
</customTypes>
<forcedTypes>
<forcedType>
<name>Mood</name>
<expression>eArray</expression>
</forcedType>
</forcedTypes>

The generated code looks like this:
public final TableField<org.test.db.jooq.shared.tables.records.test_enum_array, Mood[]> eArray = createField("eArray", org.jooq.impl.DefaultDataType.getDefaultDataType("org.test.enums.Mood").getArrayDataType(), this, "", new MoodConverter());

My MoodConverter is like so:
public class MoodConverter extends EnumConverter<String, Mood> {
public BaseTypeIdConverter() {
super(String.class, Mood.class);
}
}

But IntelliJ throws up a 'Cannot resolve method' IDE warning.

I can see that MoodConverter requires a string but appears to be getting passed the entire array so that's clearly wrong.  However, I'm not clear on how to configure Jooq to give me the result I want.

Thanks!

Emrul Islam

unread,
Jun 17, 2016, 9:29:21 PM6/17/16
to jOOQ User Group
Oops, cut & paste error - my MoodConverter is so:

public class MoodConverter extends EnumConverter<String, Mood> {
    public MoodConverter() {
super(String.class, Mood.class);
}
}

Lukas Eder

unread,
Jun 19, 2016, 2:04:52 PM6/19/16
to jooq...@googlegroups.com
Hmm, the interesting thing here is to see why the MOOD enum type is not generated. Did you perhaps exclude it from the code generation? Or is it in a different schema? jOOQ supports PostgreSQL enum types and enum type arrays out of the box, which is why I'm asking...

Lukas

--
You received this message because you are subscribed to the Google Groups "jOOQ User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jooq-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Emrul Islam

unread,
Jun 20, 2016, 7:18:23 AM6/20/16
to jOOQ User Group
Hi Lukas,

An existing enum is generated and it works fine.  However, I want to map to an Enum defined in an external package.


Thanks

Lukas Eder

unread,
Jun 29, 2016, 9:13:24 AM6/29/16
to jooq...@googlegroups.com
Oh, I see, thanks for sharing.

Here's what I can tell what the problems are:

1. You're trying to bind String to the <T> type of the converter. That's not correct. If you want to use a Converter, then it will have to be converting from the generated Mood Enum type to your own custom Mood Enum type. As it stands now, for some reason, the generated enum type is absent in your configuration, probably because you excluded it. You'll have to re-enable that generation.
2. If you really don't want any jOOQ-generated enums, then you will need to use a data type binding (http://www.jooq.org/doc/latest/manual/sql-building/queryparts/custom-bindings/) rather than a converter. jOOQ won't know how to bind the JDBC type correctly to JDBC (including casts, etc.). You'll have to do that manually. But then again, why not use jOOQ's built-in enum support...
3. Your <U> type is Mood[], not Mood. You need to implement a converter that can handle enum arrays, not individual enum values.

I hope this helps already. Let me know if you encounter any further issues.
Lukas

ja...@redcurrent.com

unread,
Feb 17, 2017, 3:15:45 AM2/17/17
to jOOQ User Group
Hi Lukas;

Thanks for all your awesome work on jOOQ!

I'm having troubles finding any working example of how to create a converter or a custom binding from a postgres enum to the jooq generated enum (or some means to use only the enum we have defined elsewhere).

Because we share code with clients, we can't use the generated enum, and so far, the only way to get jOOQ to play nice is to change all of our postgres ENUM types to raw text to get support for a Converter<String, MyEnum>.

What does it take to get conversion from <MyEnum, JooqsMyEnum> ?  

I'm not opposed to writing my own Binding, however, this looks non-trivial, and would rather here from the source than muddle something together that might work for my test cases, but fail around some unconsidered edge case.

Thank you!

Lukas Eder

unread,
Feb 17, 2017, 3:22:35 AM2/17/17
to jooq...@googlegroups.com
Hi James

2017-02-17 1:36 GMT+01:00 <ja...@redcurrent.com>:
Hi Lukas;

Thanks for all your awesome work on jOOQ!

I'm having troubles finding any working example of how to create a converter or a custom binding from a postgres enum to the jooq generated enum (or some means to use only the enum we have defined elsewhere).

The Postgres generated enum doesn't need any custom binding, it is bound correctly by the built-in DefaultBinding

There's an example for how to use the built-in EnumConverter class at the bottom of this page of the manual:
 
Because we share code with clients, we can't use the generated enum, and so far, the only way to get jOOQ to play nice is to change all of our postgres ENUM types to raw text to get support for a Converter<String, MyEnum>.

What does it take to get conversion from <MyEnum, JooqsMyEnum> ?  

I'm not opposed to writing my own Binding, however, this looks non-trivial, and would rather here from the source than muddle something together that might work for my test cases, but fail around some unconsidered edge case.

You can simply write a Converter<JooqsMyEnum, MyEnum> (where <T, U> means <T = Database type, U = Your type>) and set the code generator to apply that using forcedType configuration:

You could do it with a Binding instead of a Converter, too, but that would be a bit more complicated.

Let me know if you have any specific questions or if something isn't working for you, and I'll be more than happy to help.
Lukas

ja...@redcurrent.com

unread,
Feb 21, 2017, 3:26:03 PM2/21/17
to jOOQ User Group
Heya.

I realize now that part of my problem is build dependency race conditions.

I can't write Converter<JooqEnum, MyEnum> because our persistence utilities live in a module that is built before the module which generates jooq.

The reasons for this are long and ugly, but suffice to say that in order to share code with clients, and not have a totally hacked build, it has to be this way
(we can't compile our DbGenerator with references to types that are generated by the DbGenerator).

What I wound up doing is writing a converter that resorts to some ugly reflection to avoid the dependency on a type that does not exist yet:


public class VisibilityConverter<T extends EnumType> implements Converter<T, Visibility> {

private static class GeneratedTypeReflection {

private final Lazy<Class<?>> cls;
private final Lazy<Method> valueOf;

private GeneratedTypeReflection(String type) {
this.cls = Lazy.deferredUnsafe(()->getClass().getClassLoader().loadClass(type));
this.valueOf = Lazy.deferredUnsafe(()->cls.get().getMethod("valueOf", String.class));
}
}

private final GeneratedTypeReflection type;

public VisibilityConverter() {
type = new GeneratedTypeReflection("com.redcurrent.data.sql.generated.enums.RcVisibility");
}

@Override
public Visibility from(T v) {
if (v == null) {
return null;
}
return Visibility.valueOf(v.getLiteral());
}

@Override
public T to(Visibility v) {
if (v == null) {
return null;
}
try {
return (T) type.valueOf.get().invoke(null, v.name());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public Class<T> fromType() {
return Class.class.cast(type.cls.get());
}

@Override
public Class<Visibility> toType() {
return Visibility.class;
}
}

Then standard custom converter / forced type semantics on the above Visibility class.

new ForcedType()
.withName(Visibility.class.getCanonicalName())
.withExpression("visibility")
.withUserType(Visibility.class.getCanonicalName())
.withConverter(VisibilityConverter.class.getCanonicalName())
,

new CustomType()
.withName(Visibility.class.getCanonicalName())
.withConverter(VisibilityConverter.class.getCanonicalName())

It may be worthwhile to adopt this pattern into a Jooq ReflectionEnumConverter which takes the fully qualified name of the expected generated enum type;
I will likely make such an abstraction during code review, but thought I would share in case others have issues with being able to reference the jooq enum.

Lukas Eder

unread,
Feb 27, 2017, 4:35:34 PM2/27/17
to jooq...@googlegroups.com
Hi James,

Hmm, I'm not sure if this is a desireable approach for the jOOQ libraries. The recommended way to proceed here is to have this dependency tree :

your code
+ your generated code
+-+ your converters
  +-- jooq

your code generator
+-+ your converters
  +-- jooq

So, in other words, your converters should be built in their own, separate module that you can depend on from both your code and from your code generator.

That seems to be the cleanest option in my opinion, unless I'm missing something?
Cheers
Lukas

--
You received this message because you are subscribed to the Google Groups "jOOQ User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jooq-user+unsubscribe@googlegroups.com.

ja...@redcurrent.com

unread,
Feb 28, 2017, 12:53:05 AM2/28/17
to jOOQ User Group
 Hm. 

Our beans live in a module by themselves.

I want to bind the enums in that module to use our existing enum type.

In order to reference the generated jooq enum types, the converters need to be in the same module as the generated code.
That is, they cannot reference the generated enum until it exists, and the generator won't compile without the converter types properly resolved.

Unless it is possible to generate the enum types first in one module, then reference them in a later module which registers converters, I am in a bit of chicken-and-egg problem.
That or I don't understand how to tell jooq to just straight up use my enum entirely and not own it's own copy (which I can't reference in converter until it exists, and can't generate without my converters on classpath).

Lukas Eder

unread,
Feb 28, 2017, 2:25:37 AM2/28/17
to jooq...@googlegroups.com
It's a chicken-and-egg problem only because you're trying to trick Maven into doing something it wasn't designed for. You simply cannot (should not?) reference types that are not really available yet in a code generator.

Perhaps you did find somewhat of a loophole and it does work to some extent, but there's risk of your solution breaking again.

This is the reason why the jOOQ code generator never references any types but only type names (e.g. what you put in forcedType / userType). This way, it doesn't actually have to *know* the Class reference until the generated code is compiled *after generation*

It seems to me that you can easily solve your problem by adhering to a naming convention:
new ForcedType()
.withName(Visibility.class.getCanonicalName())
.withExpression("visibility")
.withUserType(Visibility.class.getCanonicalName())
    .withConverter("com.example." + Visibility.class.getSimpleName() + "Converter")
Hope this helps,
Lukas

Reply all
Reply to author
Forward
0 new messages