> Is there a way to generate the same ENUM Class when using JOOQ code
> generator?
In jOOQ 2.0.1, I have added experimental support for custom-defined
enums in the source code generator. Here is the relevant release
announcement:
https://groups.google.com/forum/?fromgroups#!searchin/jooq-user/enum/jooq-user/rxMlFfu7LZk/KzIyDZ7NqKMJ
Unfortunately, not all integration tests for custom-defined enums pass
and I have not yet found the time to fix those failing tests yet. That
is why this feature is not yet officially documented.
For MySQL, however, most integration tests run smoothly. So if you're
willing to beta-test this, you can give it a shot! There is an example
jooq-codegen configuration here:
https://github.com/lukaseder/jOOQ/blob/master/jOOQ-test/configuration/org/jooq/configuration/lukas/mysql/library.properties
The two essential lines you'll have to add to your code generation
configuration properties file are these:
# Declare your own custom enum type (doesn't have to exist in the database)
# In this case, the "synthetic" type is called BOOLEAN_YN_UC
# and its values are Y and N (a comma-separated list of values)
generator.database.enum-type.BOOLEAN_YN_UC="Y",N
# Force that custom enum type upon all columns / parameters / attributes
# that match a given regular expression. In this case,
# table T_BOOLEANS
# column Y_N_UC
generator.database.forced-type.BOOLEAN_YN_UC=(?i:(.*?\.)?T_BOOLEANS\.Y_N_UC)
Any feedback is welcome!
Cheers
Lukas
my feedback is ~
maybe the enum column could be excluded automatically when it'd be forced.
(2) naming convention is changed.
For example:
generator.database.enum-type.MyPriorityLevel="Low","Normal", "High"
my expected Enum Name is "MyPriorityLevel"
but the enum name is generated to be "Myprioritylevel"
My feedback is
Could the custom enum typ keep User's naming convention?
Thanks.
-Pay.
Thanks for your feedback! Glad it works for you
> (1) I also need to exclude the column matching the forced-type.
Yes, I'll file this as a bug:
https://sourceforge.net/apps/trac/jooq/ticket/1137
In MySQL, the orginial enum type can then be omitted.
> (2) naming convention is changed.
> generator.database.enum-type.MyPriorityLevel="Low","Normal", "High"
Write any of these:
generator.database.enum-type.MY_PRIORITY_LEVEL="Low","Normal", "High"
generator.database.enum-type.my_priority_level="Low","Normal", "High"
This behaves just like any other database artefacts, where
underscore_separated_words are transformed to CamelCase by
jOOQ-codegen
Cheers
Lukas
Low(1, "Low"),
Normal(2, "Normal"),
High(3, "High");
.....
> very thanks!! Now, I'm having CamelCase for enum class name. very nice!
Glad it works!
> For instance:
> (A) select * from someTable where priority_level = 1;
> (B) select * from someTable where priority_level = 'Low';
>
> (A) and (B) get the same data.
I didn't know about this feature in MySQL.
> What I'm thinking ...is it possible to get a enum Type Class with ID number,
> like below....
Java's Enum class provides an ordinal() method. For instance,
MyPriorityLevel.Low.ordinal() == 0
MyPriorityLevel.Normal.ordinal() == 1
MyPriorityLevel.High.ordinal() == 2
The difference is that Java starts counting at 0, whereas MySQL starts
at 1, 0 being reserved for "NULL". Would that work for you? Other than
that, could you show me a use-case where such a synthetic ID is
useful? MySQL is the only database that has this feature, so I'm a bit
reluctant to add support for it in this way...
Cheers
Lukas
> Let me think how to describe that use-case....
>
> my running project is presented by java web. Sometime, I need to pass param
> by ID in POST or GET Data. [...]
I understand.
> I'm used to create this kind of reverse function inside ENUM Class.
> Otherwise, for the Enum class from JOOQ, I'd create an utility class for
> looking up the ID of enum type.
I'd favour this solution over code generation for this specific case.
Right now, you can do this:
MyPriorityLevel.values()[index - 1]
But you probably want some fail-safety for wrong indexes and some null
handling. So what do you think about this implementation in
org.jooq.util.mysql.MySQLFactory:
------------------------------------------------
public static <E extends java.lang.Enum<E> & org.jooq.EnumType> E
enumType(Class<E> type, int index) {
if (index <= 0) {
return null;
}
E[] values = type.getEnumConstants();
if (index > values.length) {
return null;
}
return values[index - 1];
}
------------------------------------------------
Another option might be to consider Minuteproject for source code
generation. It has some nice enhancement for enums too:
http://minuteproject.wikispaces.com/JOOQ#toc8
This is a third-party product that has started to integrate with jOOQ
recently. You'd be a beta tester :-)
Cheers
Lukas
> (I'm not good on PostgreSQL. From Document, PostgreSQL seems not to support
> query by ID...)
I'm not aware of this, but I'm not so experienced with Postgres either
> But, you still also give me a good Generic utility from mySqlFactory,
> thanks!
> I think it's good enough. I totally agree on this utility for mySql.
This will be implemented in jOOQ 2.0.5:
https://sourceforge.net/apps/trac/jooq/ticket/1152
> I took a look at "MinuteProject 4 JOOQ". Its enum type seem not to support
> looking-up ID. or I missed something.
MinuteProject allows you to use custom velocity templates for source
code generation, so you can customise your generated source. I have to
try it myself too, though
Cheers
Lukas
> You're right. not all RDBMS support ENUM type. It's not good idea to let
> ENUM class have ID value.
> The number of enum type in mySql is beginning with 1. Maybe other RDBMS
> would not. even no support number.....
I am not sure I understand the issue here, but it may be similar to
something we do.
In our database, we store all sorts of enumerations as numbers.
There are no particular checks in the database that enforces valid
values, but they are manipulated as Java enums in the application. Our
enums may contain sequential or non-sequential values, in some cases
may have negative values, holes, null allowed. The Java enum enforces
those conditions.
Here is a standard enumeration, with a simple numbering logic:
public enum CivilStatus implements NumberBasedEnum {
SINGLE(1, "Single"),
MARRIED(2, "Married"),
DIVORCED(3, "Divorced"),
WIDOW(4, "Widow"),
;
private static EnumRegistry<CivilStatus> registry = new
EnumRegistry<>(CivilStatus.class);
private String name;
private CivilStatus(int intValue, String name) {
this.name = name;
if(ordinal() != intValue - 1) {
throw new IllegalStateException("The ordinal value must be
the same as the constant value - 1!");
}
}
@Override
public int intValue() {
return ordinal() + 1;
}
@Override
public String toString() {
return name;
}
public static CivilStatus getEnum(int enumvalue) {
return registry.getEnum(enumvalue);
}
}
The interface is very simple:
public interface NumberBasedEnum {
public int intValue();
}
The EnumRegistry class builds an internal map using
"EnumSet.allOf(enumClass)". It even has other methods like "public T
getEnum(String iName)". It also validates that intValues are unique.
We have about 200-300 such enums, so if jOOQ allowed us to say that
column X of table Y should be mapped to a number-based enum, it would
be excellent. I guess it would mean adding the getEnum(int) method to
the super interface, and maybe have to generify that super interface
but these are implementation considerations.
Appologies if I misunderstood and was off-topic.
Cheers,
-Christopher
> Appologies if I misunderstood and was off-topic.
This isn't off-topic. You're right on track.
> We have about 200-300 such enums, so if jOOQ allowed us to say that
> column X of table Y should be mapped to a number-based enum, it would
> be excellent. I guess it would mean adding the getEnum(int) method to
> the super interface, and maybe have to generify that super interface
> but these are implementation considerations.
This will be possible in some future version. But as I previously
mentioned in this thread, jOOQ's type system isn't ready for such
mapping yet, hence I'm not officially supporting these undocumented
features. But I can see the need for it.
Implementation hints / contributions are very welcome!
Cheers
Lukas
What about:
1. A mandatory super interface for all enumerations that jOOQ can recognise:
public static interface NumberBasedEnum<V extends Enum<V>> {
public int intValue();
public V getEnum(int intValue);
}
2. Add a method in SQLDataType to expose creating such enum types:
public static <X extends NumberBasedEnum<?>> SQLDataType<X>
createNumberBasedEnumType(Class<X> enumClass) {
return new SQLDataType<>(enumClass, enumClass.toString());
}
3. Generate a field with that type according to some user
configuration (or at least user can create its own generator):
public final org.jooq.TableField<test.generated.tables.records.SomeEnum,
TestEnum> SOMEENUM = createField("SomeEnum",
SQLDataType.createNumberBasedEnumType(TestEnum.class), this);
Where TestEnum is an enum like I explained in my previous e-mail:
public enum TestEnum implements SQLDataType.NumberBasedEnum<TestEnum> {
// Constructors can specify non-clashing IDs
A,
B,
;
@Override
public int intValue() {
// Whatever logic to return intValue
return ordinal();
}
@Override
public TestEnum getEnum(int intValue) {
// Whatever logic to find the enum, could be based on registry
return values()[intValue];
}
}
If the SQLDataType mapped Java type is a NumberBasedEnum, use that
info in jOOQ code to "intValue()" or "getEnum(intValue)".
Hope this helps,
-Christopher
That would be EnumType, then:
http://www.jooq.org/javadoc/latest/org/jooq/EnumType.html
> public static interface NumberBasedEnum<V extends Enum<V>> {
> public int intValue();
> public V getEnum(int intValue);
> }
I think, intValue() would have to be generic...:
public interface EnumType<T> {
public T getLiteral(); // The existing literal corresponds to intValue()
}
public V getEnum() would have to be made static and moved to some
registry. It doesn't make sense in an interface, I think.
> 2. Add a method in SQLDataType to expose creating such enum types:
That would correspond to DataType.asEnumDataType():
http://www.jooq.org/javadoc/latest/org/jooq/DataType.html#asEnumDataType%28java.lang.Class%29
> 3. Generate a field with that type according to some user
> configuration (or at least user can create its own generator):
> public final org.jooq.TableField<test.generated.tables.records.SomeEnum,
> TestEnum> SOMEENUM = createField("SomeEnum",
> SQLDataType.createNumberBasedEnumType(TestEnum.class), this);
You're a magician! That would then correspond to that aforementioned
undocumented feature! Check out <enumTypes/> and <forcedTypes/> tags
in jOOQ 2.0.4's code generation configuration:
http://www.jooq.org/xsd/jooq-codegen-2.0.4.xsd
> Hope this helps,
Well... :-)
Seeing that you have very similar ideas is promising, as I am probably
on the right track with this existing undocumented feature. These are
the missing things:
- The "base type" for jOOQ's EnumType is lost, i.e. the synthetic
EnumType has no reference to its original DataType or to the generic
type <T>. This means, only VARCHAR enum types work right now (and some
others in lenient, "forgiving" databases)
- EnumType only knows its "literal". There is currently now way to add
another "synthetic index" as you and Pay propose.
Cheers
Lukas
PS: For further reference, this was the original discussion that lead
to these features:
https://groups.google.com/forum/#!topic/jooq-user/AC4ZbtBIQ6A/discussion
It depends. If it is, then you end up with Integer types for something
that cries for int primitive types (very common). In my case, I would
have to re-implement all my enums (200-300) which I would not do.
Let's be practical. In the world, there are only few real enumeration types:
- int values.
- String values.
- Eventually char values (e.g.: Y/N), which are like Strings of length one.
I don't deny that someone could make an EnumType<Date>, but I guess
you get my point :)
> public interface EnumType<T> {
> public T getLiteral(); // The existing literal corresponds to intValue()
> }
Having answered my feeling on such a signature for int values, I will
elaborate on the impact of user code. Considering that these are enums
used in actual user code, I can bet that we would soon see:
if(myEnum.getLiteral() != null) {
// Do something.
}
because the problem with type wrapper is that they convey the null
possibility. I am sure some integrists would even go as far as using:
myEnum.getLiteral().intValue()
> public V getEnum() would have to be made static and moved to some
> registry. It doesn't make sense in an interface, I think.
True of course, I was too quick crafting the code.
My DB enums all have a static getEnum(int) method which is within the
enum itself. It would be more desirable to make closer the enum and
its lookup code rather than having to build a central registry.
Maybe you could have a contractual API: if a getEnum method is
available through reflection (loaded only once per enum and cached so
not a performance issue), then this API is used. If not, it would
iterate on the values() until intValue() returns the expected one.
Performance improvements would just be a matter of implementing the
getEnum method. Alternatively (or in addition if the getEnum method is
not declared), jOOQ could iterate once per enum and establish that
mapping once and for all, though I don't know if that can have nasty
performance issues in practical case (I doubt it).
> - The "base type" for jOOQ's EnumType is lost, i.e. the synthetic
> EnumType has no reference to its original DataType or to the generic
> type <T>. This means, only VARCHAR enum types work right now (and some
> others in lenient, "forgiving" databases)
Hence the practical approach I described:
- VARCHAR enums -> String-based enums
- NUMBER enums -> int-based enums
- CHAR enums -> char-based enums.
-Christopher
Sorry for the delay. This E-Mail was lost in my mailbox
> Let's be practical. In the world, there are only few real enumeration types:
> - int values.
> - String values.
> - Eventually char values (e.g.: Y/N), which are like Strings of length one.
You're probably right. And since jOOQ doesn't support
Field<Character>, CHAR(1) and VARCHAR(1) and similar are also String
values. Thinking about it, the JPA @Enumerated annotation only
distinguishes between ordinal and string enum literals, too:
http://docs.oracle.com/javaee/6/api/javax/persistence/Enumerated.html
Speaking of which, this annotation should be rendered in generated
POJOs with <pojos/> and <jpaAnnotations/> both set to true. That's
https://sourceforge.net/apps/trac/jooq/ticket/1197
> Maybe you could have a contractual API: if a getEnum method is
> available through reflection (loaded only once per enum and cached so
> not a performance issue), then this API is used. [...]
This feels a bit fuzzy. I'd like to re-use the existing EnumType
interface and its getLiteral() attribute, even if itis nullable for
Integer wrappers. In fact there's nothing wrong with that, as NULL is
an acceptable enumeration value in SQL and jOOQ doesn't distinguish
nullable and not-nullable fields, so far.
Other options than making EnumType<T> generic are likely to introduce
incompatibilities for my MySQL and Postgres users, where true enums
(as opposed to synthetic ones) already exist. Besides, there can still
be static lookup methods outside of the enum, as implemented for Pay
in MySQLFactory.
> If not, it would
> iterate on the values() until intValue() returns the expected one.
> Performance improvements would just be a matter of implementing the
> getEnum method. Alternatively (or in addition if the getEnum method is
> not declared), jOOQ could iterate once per enum and establish that
> mapping once and for all, though I don't know if that can have nasty
> performance issues in practical case (I doubt it).
Enum.valueOf() works in similar ways. It maintains a global dictionary
that is maintained in java.lang.Class.enumConstantDirectory. I can get
some inspiration there...
Cheers
Lukas
>> Let's be practical.
> You're probably right.
but
> I'd like to re-use the existing EnumType
> interface and its getLiteral() attribute
So the real question is: do you want jOOQ to influence the global
design of an application or do you want jOOQ to help interfacing with
the database without being too intrusive in the application that is
using it? I can tell you that enumerations propagate far from the
simple database layer.
If you want to influence the application design, then of course you
can make up the rules.
If you want to help users simplify their database access, then re-use
their enums. If that means the generator should create a converter,
where users can define the rules for the bridge, then why not.
In practice, people build int enums the same way throughout an
application so generating a bridge would be simple. These enums tend
to have simple logic and once used in a program that rule cannot
change (use of the intValue() method to switch/case, or null checks if
toInteger(), etc.). I bet no jOOQ user has ever named that method
getLiteral(): they are not library makers but library users :)
Personaly, my team would never migrate to a scheme that mandates
changing the enumeration logic. This is too risky for our application
for various reasons. The most tolerable intrusion would be renaming
the method that returns the int value and/or implement an interface,
though "getLiteral" would not mean much to anyone which complexifies
the code.
Let me expand on a hypothetical generator-based solution, which I
think could cover all enum types (String, int, Integer, Date if you
like, etc.).
1. Say we have this xx.SomeEnum declaration in a codebase:
public enum SomeEnum {
public intValue() {...}
public static SomeEnum getEnum(int x) {...}
}
2. Then specify to the generator the logic for each enum:
enum: xx.SomeEnum
valueType: int
valueGetter: intValue
enumLookup: getEnum
sqlDataType: SMALLINT
3. I declare for the generator the column mapping of that enum:
TableX.ColumnY: xx.SomeEnum
4. The generator builds a central lookup class to convert the value
to/from an object.
This would be used by jOOQ when using/retrieving a value to/from the DB.
public class EnumLookup {
private final Map<Enum, EnumMapper<>> map = new HashMap<>();
public EnumLookup() {
map.put(SomeEnum.class, new EnumMapper<SomeEnum, Integer> {
public SomeEnum toEnum(Integer x) {
return SomeEnum.getEnum(x); // Autobox
}
public Integer fromEnum(SomeEnum e) {
return e == null? null: e.intValue(); // Autobox
}
public SQLDataType getDataType(SomeEnum e) {
return SQLDataType.SMALLINT;
}
}
}
public <T extends Enum<T>> EnumMapper<T, Object> getMapper(Class<T> clazz) {
return map.get(clazz);
}
}
Using the EnumMapper interface:
interface EnumMapper<T extends Enum<T>, U> {
public T toEnum(U x);
public Integer fromEnum(T e);
public SQLDataType getDataType(T e);
}
5. Use SomeEnum in field signatures
public final org.jooq.TableField<test.generated.tables.records.SomeRecord,
SomeEnum> MY_ENUM = createField("MyEnum",
SQLDataType.createEnumType(SomeEnum.class), this);
6. Use the lookup when marchalling/unmarshalling the enum.
This work for any type of literal, whether they are object or
primitive type. Bonus: we have a central place where we see all enum
declarations and how they get translated.
Other things could get added to the generator description like whether
a null enumeration is allowed (when an object literal is used).
> Other options than making EnumType<T> generic are likely to introduce
> incompatibilities for my MySQL and Postgres users, where true enums
> (as opposed to synthetic ones) already exist.
I am sure the mapper solves these kind of issues, by boxing/unboxing
(basically adapting) primitive to objects.
> Besides, there can still
> be static lookup methods outside of the enum, as implemented for Pay
> in MySQLFactory.
Maybe my naive generator descriptor should be enhanced to support
defining a helper class from which to look up if not directly
available on the enum itself. I believe this can be solved with the
above solution.
> Enum.valueOf() works in similar ways. It maintains a global dictionary
> that is maintained in java.lang.Class.enumConstantDirectory. I can get
> some inspiration there...
Yes, and EnumLookup (or rather, the mapper if it is turned into an
abstract class), could aggregate the values in a map at the first call
to avoid going many times to potentially avoid calling many times
badly implemented implementations of getEnum(x).
Please let me know what you think!
-Christopher
After sending my e-mail, I thought of various ideas. I had one that
could simplify the whole issue of enums and more.
But first, let me explain something I found which is not about enums
but which happens to be covered by what I have in mind.
In our application, we do not use the sql Date objects. Instead, we
have our own class, and because of the number of dates we keep in
memory (think time series) we keep a cache of those dates.
When using jOOQ, we would have to manually transform our Date objects
to the sql dates when making a jOOQ call, and vice versa when parsing
the record.
What if jOOQ could apply a transformation not just for enums but also
on those other objects?
1. Let's start with a type mapper interface:
interface TypeMapper<T, U> {
public T to(U x);
public U from(T e);
public SQLDataType getDataType(T e);
}
2. In the generator, let's define certain columns of certain tables to
use type mapping:
MyTable1.MyDateColumn: com.xx.CustomDate
MyTable2.MyOtherDateColumn: com.xx.CustomDate
MyTable2.MyEnum: com.xx.SomeEnum
3. The generator declares the field as a mapped type.
public final org.jooq.TableField<test.generated.tables.records.MyDateColumnRecord,
CustomDate> MY_DATE_COLUMN = createField("MyDateColumn",
SQLDataType.createMappedType(CustomDate.class), this);
4. In the code, the user configures the Settings with the right binding:
Settings settings = new Settings();
TypeMapper customDateMapper = new TypeMapper<com.xx.CustomDate,
java.sql.Date>() {
public com.xx.CustomDate to(java.sql.Date d) {
return xxx;
}
public java.sql.Date from(com.xx.CustomDate d) {
return xxx;
}
public SQLDataType getDataType(T e) {
return SQLDataType.DATE;
}
};
settings.map(MyTable1.MyDateColumn, customDateMapper);
settings.map(MyTable2.MyOtherDateColumn, customDateMapper);
TypeMapper myEnumMapper = new TypeMapper<com.xx.SomeEnum, Integer>() {
public SomeEnum to(Integer i) {
return xxx;
}
public Integer to(SomeEnum e) {
return xxx;
}
public SQLDataType getDataType(T e) {
return SQLDataType.SMALLINT;
}
}
settings.map(MyTable2.MyEnum, myEnumMapper);
The mapping configuration is up to the user, and can be tweaked if
needed (cache handling, same custom type with different mapping logic
depending on column, etc.). The user can place the mapping code
wherever he wants, and can implement enums the way he likes because he
controls the mapping.
When jOOQ tries to map a value and a mapper cannot be found, an
exception is thrown. This is not a problem because to start using a
mapped type the user would have had to define the mapping in the first
place.
Please, let me know what you think of this version :)
-Christopher
What I think?
I think I'm about to re-implement Hibernate!
But I like your ideas, keep going!
So to put it short, the type-mapping configuration options would look like this:
Master data type:
- input: table/column/pk column
- output: generated enum with two value attributes
- applied to: all referencing foreign keys
Vendor-specific enum:
- input: DB-enum type
- output: generated enum with one value attribute
- applied to: all referencing columns
Synthetic enum:
- input: list of literals
- output: generated enum with one value attribute
- applied to: all columns/attributes/parameters matching a regex
Custom enum:
- input: enum type/data type/value attribute accessor
- output: enum type mapper
- applied to: all columns/attributes/parameters matching a regex
Custom type:
- input: custom type/data type/custom type mapper
- output: no specific output
- applied to: all columns/attributes/parameters matching a regex
Sounds nice!
True, there is a line to draw somewhere. But if you are ready to map
enums, then you could as well map any type. That would greatly
simplify the user's life if they can define the bridge between their
type and the SQL type and have the jOOQ table fields using that user
type.
> So to put it short, the type-mapping configuration options would look like this:
I am not sure why you make a specific distinction between custom enums
and custom types. A custom enum happens to be a custom type in my
view.
It is also unclear in your e-mail how the mapper is defined (in the
generator or by users in their code).
I tend to think that in-code definition is more flexible, allows all
sort of mapping rules (with caching, boxing, etc) than trying to
generate such code. Moreover, the user needs to establish the mapping
rules whatever approach but they do not need to refer to model
generation related files and do not need to re-generate if a
refactoring changes their internal enum/types mapping logic.
jOOQ could provide a default enum mapper for enums that implement a
particular interface and in which mapper a map is built for later
lookup.
With a code approach, their needs to be a clear place where to
establish the mappers. It could be registered in the Settings (like in
my previous e-mail), or it could also be in the field itself:
MyDateMapper myDateMapper = new MyDateMapper();
MyTable1.MyDateColumn.setMapper(myDateMapper);
MyTable2.MyOtherDateColumn.setMapper(myDateMapper);
TypeMapper myEnumMapper = new TypeMapper<com.xx.SomeEnum, Integer>() {
public SomeEnum to(Integer i) {
return xxx;
}
public Integer to(SomeEnum e) {
return xxx;
}
public SQLDataType getDataType(T e) {
return SQLDataType.SMALLINT;
}
}
MyTable2.MyEnum.setMapper(myEnumMapper);
// Assume MyJOOQEnumImpl implements JOOQEnum interface with getLiteral()
MyTable3.MyOtherEnum.setMapper(new JOOQEnumMapper(MyJOOQEnumImpl.class));
The above code would have to be called once when the application
starts and it would be the user's responsibility.
Consider my code where I have about 200-300 enums. I would have
200-300 mapping lines, but they would be the same: I would only have
to create my generic custom enum mapper (similar to the JOOQ enum
described above) and instanciate it for every enum. Very readible in
my view (don't forget that the field and the mapper are typed, so I
cannot plug a wrong mapper on that field).
Hope this helps,
-Christopher
A mapper for enums can be generated by jOOQ with the slightly adapted
design you originally proposed. It will be useful to 80% of the users
wanting to provide jOOQ with meta-information about their custom enum
types. It will be very simple to configure and to use, there is little
effort involved.
A mapper for arbitrary custom types cannot be generated. You can still
consider your custom enums to be custom types and define your custom,
non-generated mappers though. This is for power-users wanting to go
much farther than just mapping simple enum types, like Pay (and
others) originally did.
Separating the two notions helps keeping things simple and focused for
enum types. I am not aware of any JPA implementation that has a simple
solution like the enum-mapper code generation like you originally
proposed it. However, many JPA implementations provide the "power-user
solution" for arbitrary mapping.
> It is also unclear in your e-mail how the mapper is defined (in the
> generator or by users in their code).
> I tend to think that in-code definition is more flexible, allows all
> sort of mapping rules (with caching, boxing, etc) than trying to
> generate such code.
That's why it is distinguished. If you don't need the flexibility for
enums, let jOOQ generate the enum mapper. If you need it, provide jOOQ
with a mapper reference.
> Moreover, the user needs to establish the mapping
> rules whatever approach but they do not need to refer to model
> generation related files and do not need to re-generate if a
> refactoring changes their internal enum/types mapping logic.
Then they shouldn't choose to generate mappers but go for the second
approach and provide jOOQ with a custom mapper reference.
> jOOQ could provide a default enum mapper for enums that implement a
> particular interface and in which mapper a map is built for later
> lookup.
That is not possible without reflection, because of the intValue()
method. That "default enum mapper" needs to know how to map an
instance of SomeEnum to its SomeEnum.intValue() and vice versa. Of
course, generated code will extend an AbstractEnumMapper class (as
opposed to simply implementing the interface), handling some base
logic, to keep generated code slim.
> With a code approach, their needs to be a clear place where to
> establish the mappers. It could be registered in the Settings (like in
> my previous e-mail), or it could also be in the field itself:
I prefer the Field. Or more specifically, the field's DataType. I can
see the more advanced use-case for replacing the default mapper by any
compatible mapper in the Settings.
> MyTable2.MyEnum.setMapper(myEnumMapper);
> // Assume MyJOOQEnumImpl implements JOOQEnum interface with getLiteral()
> MyTable3.MyOtherEnum.setMapper(new JOOQEnumMapper(MyJOOQEnumImpl.class));
> The above code would have to be called once when the application
> starts and it would be the user's responsibility.
I'd like to avoid delegating initialisation of jOOQ's internals to
client code. That can go terribly wrong. MyTable2.MyEnum is to be
considered an immutable column reference. It shouldn't be possible to
change its configured state like this. If mappers are referenced from
a Field's DataType, however, loading the generated classes takes care
of loading (and initialising) the generated mappers automatically.
Anyway, your idea and mine aren't contradictions...
> Consider my code where I have about 200-300 enums. I would have
> 200-300 mapping lines, but they would be the same: I would only have
> to create my generic custom enum mapper (similar to the JOOQ enum
> described above) and instanciate it for every enum. Very readible in
> my view (don't forget that the field and the mapper are typed, so I
> cannot plug a wrong mapper on that field).
Yes, ultimately, this is going to the right direction.
> A mapper for enums can be generated by jOOQ with the slightly adapted
> design you originally proposed. It will be useful to 80% of the users
I agree.
> A mapper for arbitrary custom types cannot be generated. You can still
> consider your custom enums to be custom types and define your custom,
> non-generated mappers though.
Good.
Flexibility here is good: use what the library provides, but for
advanced stuff there is a way.
> I prefer the Field. Or more specifically, the field's DataType. I can
> see the more advanced use-case for replacing the default mapper by any
> compatible mapper in the Settings.
>
> I'd like to avoid delegating initialisation of jOOQ's internals to
> client code. That can go terribly wrong. MyTable2.MyEnum is to be
> considered an immutable column reference. It shouldn't be possible to
> change its configured state like this. If mappers are referenced from
> a Field's DataType, however, loading the generated classes takes care
> of loading (and initialising) the generated mappers automatically.
I am not sure how you want to ensure that mappers get initialized
before the fields when dealing with custom types.
> Anyway, your idea and mine aren't contradictions...
No, I think we are on the same train of thought now, so I am going to
be less of a pain :)
Cheers,
-Christopher
A first draft of this implementation is committed to SVN trunk and
GitHub. While implementing this, the general feeling of how the API
should look got a lot better. Essentially, the new org.jooq.Converter
type is stabilising:
https://github.com/lukaseder/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/Converter.java
Here are some facts:
1. I'm calling this type Converter rather than Mapper, as the notion
of "conversion" is already present in jOOQ's org.jooq.tools.Convert
and various other places. So this whole discussion is about custom
data type conversion.
2. I found that it is necessary for jOOQ's internals to get a hold of
both Java types involved in type conversion, i.e. the database type
(e.g. java.lang.Integer, java.sql.Date, etc) and the custom type
(com.example.MyEnum, com.example.MyDateType).
3. There are two elements to be configured in source code generation:
3.1. The "custom type" (e.g. MyEnum and MyEnumConverter)
3.2. The "forced type" (e.g. MyEnum and a regular expression matching
all columns upon which this forced type should be mapped). "Forced
types" can be used for other purposes, too. For example, forcing a
NUMBER(1) column to be java.lang.Boolean. This is currently still
experimental but should be working in 2.1.0
A sample configuration can be seen here:
https://github.com/lukaseder/jOOQ/blob/f53818504ef1d0345a8aae40fd29b7a9ccd6976a/jOOQ-test/configuration/org/jooq/configuration/lukas/hsqldb/library.xml
4. You were right, essentially, about custom enum types being a
special case of arbitrary custom types. As of now, they are treated no
differently. Some convenience in code generation may be added in the
next release, in order to be able to generate custom enum converters.
For now, there is EnumConverter, which can be extended by manually
written custom converters (overriding the to() method):
https://github.com/lukaseder/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/impl/EnumConverter.java
5. Generated tables will use the custom type as bound for <T>. A
sample table can be seen here:
https://github.com/lukaseder/jOOQ/blob/f53818504ef1d0345a8aae40fd29b7a9ccd6976a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/TMappedTypes.java
6. There is a central custom type registry providing a mapping between
MyEnum -> MyEnumConverter. This is the registry:
https://github.com/lukaseder/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/impl/DataTypes.java
It is used by jOOQ to know how to bind and inline custom types in
SQL. A custom type can only be converted by a single converter,
globally. i.e. it is currently not possible to have MyEnum ->
Converter1 in one place, and MyEnum -> Converter2 in another. The
reason for this is, jOOQ needs an unambiguous registry in
"context-less" cases like this:
create.select(MyEnum.VAL).fetch();
This will be an open point. I'm curious about your feedback on
this topic. You suggested to use the Factory's Settings as an
(additional) Converter registry. This might be a viable workaround for
a future release, if several converters are really needed for the same
custom type. I feel it might be a bit overkill, though.
7. Fields (Parameters) hold direct references to their
"ConvertedDataType". This means that when fetching data from a
ResultSet (CallableStatement, SQLInput), this information is available
directly from the Field.
I feel this is on a good track!
Here's a preliminary, simplistic test-case for these converters. It
involves custom converters for enums and java.util.Date /
java.util.Calendar:
https://github.com/lukaseder/jOOQ/blob/f53818504ef1d0345a8aae40fd29b7a9ccd6976a/jOOQ-test/src/org/jooq/test/jOOQHSQLDBTest.java#L683
Next week, I'll create many more test cases for all sorts of queries
and I'll consolidate the existing experimental functionality.
> No, I think we are on the same train of thought now, so I am going to
> be less of a pain :)
You may speak up again :-)
Cheers
Lukas
1. I'm calling this type Converter rather than Mapper
2. I found that it is necessary for jOOQ's internals to get a hold of
both Java types involved in type conversion
3.1. The "custom type" (e.g. MyEnum and MyEnumConverter)
3.2. The "forced type" (e.g. MyEnum and a regular expression matching
all columns upon which this forced type should be mapped). "Forced
types" can be used for other purposes, too. For example, forcing a
NUMBER(1) column to be java.lang.Boolean.
A sample configuration can be seen here:
https://github.com/lukaseder/jOOQ/blob/f53818504ef1d0345a8aae40fd29b7a9ccd6976a/jOOQ-test/configuration/org/jooq/configuration/lukas/hsqldb/library.xml
For now, there is EnumConverter, which can be extended by manually
written custom converters (overriding the to() method):
https://github.com/lukaseder/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/impl/EnumConverter.java
5. Generated tables will use the custom type as bound for <T>. A
sample table can be seen here:
https://github.com/lukaseder/jOOQ/blob/f53818504ef1d0345a8aae40fd29b7a9ccd6976a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/TMappedTypes.java
6. There is a central custom type registry providing a mapping between
MyEnum -> MyEnumConverter. This is the registry:
https://github.com/lukaseder/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/impl/DataTypes.java
It is used by jOOQ to know how to bind and inline custom types in
SQL. A custom type can only be converted by a single converter,
globally. i.e. it is currently not possible to have MyEnum ->
Converter1 in one place, and MyEnum -> Converter2 in another. The
reason for this is, jOOQ needs an unambiguous registry in
"context-less" cases like this:
create.select(MyEnum.VAL).fetch();
This will be an open point. I'm curious about your feedback on
this topic. You suggested to use the Factory's Settings as an
(additional) Converter registry. This might be a viable workaround for
a future release, if several converters are really needed for the same
custom type. I feel it might be a bit overkill, though.
7. Fields (Parameters) hold direct references to their
"ConvertedDataType". This means that when fetching data from a
ResultSet (CallableStatement, SQLInput), this information is available
directly from the Field.
I feel this is on a good track!
It is used by jOOQ to know how to bind and inline custom types in
SQL. A custom type can only be converted by a single converter,
globally. i.e. it is currently not possible to have MyEnum ->
Converter1 in one place, and MyEnum -> Converter2 in another. The
reason for this is, jOOQ needs an unambiguous registry in
"context-less" cases like this:
create.select(MyEnum.VAL).fetch();
>>> jOOQ needs an unambiguous registry in
>>> "context-less" cases like this:
>>> create.select(MyEnum.VAL).fetch();
>
> MyEnum is a field, so if a converter is attached to the field definition
> somehow, then it is unambiguous.
I'm not sure what you mean. In my example, MyEnum is an enum type, and
VAL is a value of type MyEnum. In other words, this query corresponds
to
SELECT 'VAL' FROM DUAL
There is no "field" involved in this case. That's why a central
registry is needed here, to find a converter for the custom type
MyEnum, which is passed to jOOQ without "context"
> Having different converters resolving to the same custom type is actually
> important and I have a practical test case.
>
> In our code base, we store 3 types of dates with the same custom type (let's
> call it MyDateType):
> - Dates, where null means a null date.
> - Dates used as min dates of ranges, where null means -infinity.
> - Dates used as max dates of ranges, where null means +infinity.
I'm not sure if I understand this design. Why would you use NULL for
something other than NULL? That smells fishy to me. Can you show some
examples where this would be practicable?
Cheers
Lukas
> What about my case where I have 2 back-ends (SQLServer and Oracle)? Let's
> say I have a BIT in one (Boolean) and a Number in the other (Short). For
> fields, you said that this would work because there is type conversion. But
> is this going to work with type mapping if I generate a model against one of
> the RDBMS and run on the other?
Once the schema is generated, a field's type will then be fixed to
Boolean for SQL Server's BIT. This will work with Oracle's NUMBER
field. You don't need a converter for that. If you want to generate
the schema from Oracle's NUMBER type, you'll have to apply a "forced
type" upon that field
> Out of curiosity, can we augment an already existing MyEnum to implement the
> Converter interface so all code is located at a single place if we want it
> to? Would jOOQ allow it?
Java enum types can implement any interface. I don't see any problem
with that. Beware that enum types cannot extend base classes for
convenience...
> What would happen if one generated schema defines certain mappings, and
> another generated schema defines other mappings but for the same types
> (especially calendars or dates)? Running with one would be fine but could
> running with two result in collisions in the registry?
The mappings' scope is not a single schema.
Cheers
Lukas
I'm not sure what you mean. In my example, MyEnum is an enum type, and
VAL is a value of type MyEnum. In other words, this query corresponds
to
SELECT 'VAL' FROM DUAL
There is no "field" involved in this case. That's why a central
registry is needed here, to find a converter for the custom type
MyEnum, which is passed to jOOQ without "context"
I'm not sure if I understand this design. Why would you use NULL for
something other than NULL? That smells fishy to me. Can you show some
examples where this would be practicable?
> Out of curiosity, can we augment an already existing MyEnum to implement theJava enum types can implement any interface. I don't see any problem
> Converter interface so all code is located at a single place if we want it
> to? Would jOOQ allow it?
with that. Beware that enum types cannot extend base classes for
convenience...
> What would happen if one generated schema defines certain mappings, andThe mappings' scope is not a single schema.
> another generated schema defines other mappings but for the same types
> (especially calendars or dates)? Running with one would be fine but could
> running with two result in collisions in the registry?
That's life with implementing interfaces... Check out
java.lang.Runnable. It ships with run()...
Anyway, maybe from() and to() aren't the best choices. I found them
somewhat confusing and difficult to explain when writing the Javadoc.
So this discussion is maybe not even necessary... ;-)
OK, I can see how NULL can be used to model +/- inf now
I think the current design isn't entirely orthogonal to what you are
now planning to think about. Anyway, using converters/mappers also as
validators is up to you. jOOQ will be oblivious of these things and
propagate your custom exception wrapped in an
org.jooq.exception.DataTypeException...
Yet, I have to re-explain about jOOQ applying some magic with a
central registry. See, <T> is bound to MyEnum in generated code. This
means that all of jOOQ's API is concerned by this. My HSQLDB test case
mentions predicates:
T_MAPPED_TYPES.DEFAULT_ENUM_NAME.equal(StringEnum.A)
The equal method will use the bound of <T> to enforce compile-time
safety. It is not possible to compare such a field with String! This
is a simple example. You may argue that org.jooq.Comparison knows both
sides of the predicate, so it can "guess" the converter from the
left-hand side. But that's not a good move, as the bound of <T> can
propagate further, using expressions like:
nvl(T_MAPPED_TYPES.DEFAULT_ENUM_NAME, StringEnum.A).equal(StringEnum.A)
The Field.equal(T) method is a convenience method for
Field.equal(Field<T>) using Factory.val(T) internally to create the
bind value for the predicate's right-hand side. This is why I chose
the original example of selecting 'VAL' from dual. It *must* be
possible to create bind values for all custom types that are somehow
registered as user types in some type conversion. In jOOQ's internals,
the <T> type of all QueryParts *must* remain consistent. It will only
be converted when inlined in SQL or when bound as bind variable. Hence
there *must* be a "magic" central registry, because it is
programmatically impossible for jOOQ to resolve all custom types
through complex SQL expressions by accessing *some* involved field
which has a specific converter attached...
Solution for your use case, you have several options:
--------------------------------------------------------
1) Don't use jooq-codegen, to generate your user-defined date types as
bounds for <T>. You can still use jOOQ's converters and convert to
java.sql.Timestamp *before* binding, and convert to your type again in
the new Record <T, U> U getValue(Field<T> field, Converter<? super T,
U> converter) method *after* fetching (i.e. jOOQ will never see your
custom type)
2) Move that sort of mapping entirely out of jOOQ. Maybe there's a
much simpler solution, as this all feels super-overengineered (at
least to me...)
3) Create dedicated types for your 3 date types: NullableDate
(null=null), LowerBoundDate (null=-inf), UpperBoundDate (null=+inf).
All three extending some base type. Then you can use a dedicated
converter for each date type. This additional type-safety might even
be useful for your application's Java code
4) (probably not practicable): You can always use a non-null value to
represent -inf/+inf. Just like Integer.MIN_VALUE and Integer.MAX_VALUE
could be used for that.
I think the current design isn't entirely orthogonal to what you are
now planning to think about.
Yet, I have to re-explain about jOOQ applying some magic with a
central registry.
Solution for your use case, you have several options:
1) Don't use jooq-codegen
2) Move that sort of mapping entirely out of jOOQ. Maybe there's a
much simpler solution, as this all feels super-overengineered (at
least to me...)
3) Create dedicated types for your 3 date types: NullableDate
(null=null), LowerBoundDate (null=-inf), UpperBoundDate (null=+inf).
All three extending some base type. Then you can use a dedicated
converter for each date type. This additional type-safety might even
be useful for your application's Java code
4) (probably not practicable): You can always use a non-null value to
represent -inf/+inf. Just like Integer.MIN_VALUE and Integer.MAX_VALUE
could be used for that.
> With the converter feature, you enter the realm of mapping business data to
> tables automatically. I never said everything could be done! But maybe
> slight modifications could allow more. You see, enforcing certain rules on
> the DB API (jOOQ exposed interface to the DB) can be very powerful, because
> you immediately know what you can do with less possibilities of errors when
> accessing/modifying data.
I agree, it can be powerful. I have a co-worker saying "jOOQ: A device
whose mystery is only exceeded by its power" :-)
> No because the end date can be used as a regular date in other contexts.
> Like "retrieve data at a date, which happends to be the end date of a
> particular time series, if not infinity". [...]
Now, this model is confusing me again ;-) But never mind. I begin to
realise that you have a subtle type system wrapped around dates that
is too hard to handle for jOOQ... That's why it's probably best to be
implemented (and validated) on a higher level.
> - Central registry is needed because in complex queries we do not have a
> particular field to work with.
> - Because registry is central, there cannot be different conversion rules.
Exactly.
> I have one more thought about mapping my date types: what if the to/from
> method of the converter had the name of the field as a parameter? Is that
> something you have in your context?
There is no "field" in the render / bind context. <T> can be bound
entirely out of context - recall select(val(MyEnum.A)). Hence, no name
available. Note, Fields / names are available in *certain* contexts,
but making that a general rule would be unwise. Such an API would
appear to be useless in most cases that are not very simple queries.
What comes to my mind now: For some well-defined cases, you could
actually use CustomCondition and CustomField and other custom types
from jOOQ:
http://www.jooq.org/manual/JOOQ/Extend/
When you create a CustomCondition, you can add whatever checks and
context you want in this condition.
> Why wouldn't you generate the registry in a particular schema (or in the
> same package and referenced by a schema) instead of an independant class?
For the same reason. The schema would be referenced by Fields /
Tables. Hence, I do not have any schema reference at render / bind
time. There is no schema in create.select(val(MyEnum.A)).fetch()
Look at custom types to be of an application-wide scope. Not to be
confused with user-defined types, which are declared on a schema-level
in the database itself...
Cheers
Lukas
Look at custom types to be of an application-wide scope. Not to be
confused with user-defined types, which are declared on a schema-level
in the database itself...