Discussion:
Enum converter with postgres array type
Emrul Islam
2016-06-18 01:25:43 UTC
Permalink
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!
--
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
2016-06-18 01:29:20 UTC
Permalink
Oops, cut & paste error - my MoodConverter is so:

public class MoodConverter extends EnumConverter<String, Mood> {
public MoodConverter() {
super(String.class, Mood.class);
}
}
Post by Emrul Islam
Hi,
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE test_enum_array
(
"eArray" mood[]
)
<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>
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());
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!
--
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.
Lukas Eder
2016-06-19 18:04:51 UTC
Permalink
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
Post by Emrul Islam
public class MoodConverter extends EnumConverter<String, Mood> {
public MoodConverter() {
super(String.class, Mood.class);
}
}
Post by Emrul Islam
Hi,
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE test_enum_array
(
"eArray" mood[]
)
<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>
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());
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!
--
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
For more options, visit https://groups.google.com/d/optout.
--
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
2016-06-20 11:18:23 UTC
Permalink
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
Post by Lukas Eder
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
Post by Emrul Islam
public class MoodConverter extends EnumConverter<String, Mood> {
public MoodConverter() {
super(String.class, Mood.class);
}
}
Post by Emrul Islam
Hi,
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE test_enum_array
(
"eArray" mood[]
)
<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>
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());
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!
--
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
For more options, visit https://groups.google.com/d/optout.
--
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.
Lukas Eder
2016-06-29 13:13:23 UTC
Permalink
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
Post by Emrul Islam
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
Post by Lukas Eder
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
Post by Emrul Islam
public class MoodConverter extends EnumConverter<String, Mood> {
public MoodConverter() {
super(String.class, Mood.class);
}
}
Post by Emrul Islam
Hi,
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE test_enum_array
(
"eArray" mood[]
)
<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>
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());
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!
--
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
For more options, visit https://groups.google.com/d/optout.
--
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
For more options, visit https://groups.google.com/d/optout.
--
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.
j***@redcurrent.com
2017-02-17 00:36:40 UTC
Permalink
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!
--
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.
Lukas Eder
2017-02-17 08:22:34 UTC
Permalink
Hi James
Post by j***@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:
https://www.jooq.org/doc/latest/manual/sql-execution/fetching/data-type-conversion
Post by j***@redcurrent.com
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:
https://www.jooq.org/doc/latest/manual/code-generation/custom-data-types

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
--
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.
j***@redcurrent.com
2017-02-21 20:26:01 UTC
Permalink
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.
--
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.
Lukas Eder
2017-02-27 21:35:32 UTC
Permalink
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
Post by j***@redcurrent.com
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
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.
--
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
For more options, visit https://groups.google.com/d/optout.
--
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.
j***@redcurrent.com
2017-02-28 05:53:04 UTC
Permalink
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).
--
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.
Lukas Eder
2017-02-28 07:25:35 UTC
Permalink
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
Post by j***@redcurrent.com
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).
--
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
For more options, visit https://groups.google.com/d/optout.
--
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.
Loading...