From 175380c1dc9178276509a9dbc5d244bf898ac04d Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 28 Aug 2024 12:56:34 +0200 Subject: [PATCH] [jOOQ/jOOQ#644] Add support for Oracle TYPE .. UNDER type hierarchies This includes: - [jOOQ/jOOQ#11371] Generate UDT.getSupertype() and UDT.getSubtypes() --- .../codegen/DefaultGeneratorStrategy.java | 25 +-- .../org/jooq/codegen/GeneratorStrategy.java | 6 + .../java/org/jooq/codegen/JavaGenerator.java | 203 +++++++++++++++++- .../java/org/jooq/meta/AbstractDatabase.java | 26 +++ .../org/jooq/meta/AbstractUDTDefinition.java | 18 ++ .../src/main/java/org/jooq/meta/Database.java | 5 + .../java/org/jooq/meta/UDTDefinition.java | 10 + jOOQ/src/main/java/org/jooq/UDT.java | 24 +++ .../java/org/jooq/impl/AbstractRoutine.java | 8 +- .../java/org/jooq/impl/DefaultBinding.java | 29 ++- .../src/main/java/org/jooq/impl/Internal.java | 8 +- .../main/java/org/jooq/impl/UDTDataType.java | 1 + jOOQ/src/main/java/org/jooq/impl/UDTImpl.java | 46 +++- 13 files changed, 360 insertions(+), 49 deletions(-) diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java index 556dac530b..5d8e25b4be 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java @@ -603,26 +603,17 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { sb.append(subPackage); } - // Record are yet in another subpackage - if (mode == Mode.RECORD) { + // Other subpackages + if (mode == Mode.RECORD) sb.append(".records"); - } - - // POJOs too - else if (mode == Mode.POJO) { + else if (mode == Mode.RECORD_TYPE) + sb.append(".recordtypes"); + else if (mode == Mode.POJO) sb.append(".pojos"); - } - - // DAOs too - else if (mode == Mode.DAO) { + else if (mode == Mode.DAO) sb.append(".daos"); - } - - // Interfaces too - else if (mode == Mode.INTERFACE) { + else if (mode == Mode.INTERFACE) sb.append(".interfaces"); - } - @@ -679,6 +670,8 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { result.append("Dao"); else if (mode == Mode.INTERFACE) result.insert(0, "I"); + else if (mode == Mode.RECORD_TYPE) + result.append("RecordType"); else if (mode == Mode.PATH) result.append("Path"); diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java index d85696f72a..44348dc1bb 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java @@ -616,6 +616,12 @@ public interface GeneratorStrategy { */ RECORD, + /** + * The record type mode. This is used when a {@link UDTDefinition}'s + * record type interface is being rendered. + */ + RECORD_TYPE, + /** * The pojo mode. This is used when a {@link TableDefinition}'s pojo * class is being rendered diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java index 709ce90652..8230b6cfd0 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -154,6 +154,7 @@ import org.jooq.TableOptions; // ... import org.jooq.UDT; import org.jooq.UDTField; +import org.jooq.UDTRecord; import org.jooq.UniqueKey; import org.jooq.UpdatableRecord; import org.jooq.codegen.GenerationUtil.BaseType; @@ -779,6 +780,7 @@ public class JavaGenerator extends AbstractGenerator { if (generateUDTs()) { generateUDTRecords(schema); + generateUDTRecordTypes(schema); if (generatePojos()) generateUDTPojos(schema); @@ -1712,6 +1714,15 @@ public class JavaGenerator extends AbstractGenerator { if (generateInterfaces()) interfaces.add(out.ref(getStrategy().getFullJavaClassName(tableUdtOrEmbeddable, Mode.INTERFACE))); + if (tableUdtOrEmbeddable instanceof UDTDefinition u) { + if (u.getSupertype() != null || !u.getSubtypes().isEmpty()) { + interfaces.add(out.ref(getStrategy().getFullJavaClassName(u, Mode.RECORD_TYPE)) + + "<" + + className + + ">"); + } + } + if (scala) { if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) out.println("%sclass %s extends %s[%s](%s.%s.getDataType.getRow)[[before= with ][separator= with ][%s]] {", @@ -2169,6 +2180,104 @@ public class JavaGenerator extends AbstractGenerator { out.println("}"); } + protected void generateUDTRecordType(UDTDefinition udt) { + JavaWriter out = newJavaWriter(udt, Mode.RECORD_TYPE); + log.info("Generating record type", out.file().getName()); + generateRecordType0(udt, out); + closeJavaWriter(out); + } + + private final void generateRecordType0(UDTDefinition udt, JavaWriter out) { + final String className = getStrategy().getJavaClassName(udt, Mode.RECORD_TYPE); + final List interfaces = out.ref(getStrategy().getJavaClassImplements(udt, Mode.RECORD_TYPE)); + + interfaces.add(out.ref(UDTRecord.class) + ""); + + if (udt.getSupertype() != null) + interfaces.add(out.ref(getStrategy().getFullJavaClassName(udt.getSupertype(), Mode.RECORD_TYPE)) + ""); + + printPackage(out, udt, Mode.RECORD_TYPE); + generateUDTRecordTypeClassJavadoc(udt, out); + + printClassAnnotations(out, udt, Mode.RECORD_TYPE); + + if (scala) + out.println("%strait %s[R <: %s[R] ] extends [[%s]] {", visibility(), className, UDTRecord.class, interfaces); + else if (kotlin) + out.println("%sinterface %s> : [[%s]] {", visibility(), className, UDTRecord.class, interfaces); + else + out.println("%sinterface %s> extends [[%s]] {", visibility(), className, UDTRecord.class, interfaces); + + List> typedElements = getTypedElements(udt); + for (int i = 0; i < typedElements.size(); i++) { + TypedElementDefinition column = typedElements.get(i); + + if (!generateImmutableInterfaces()) + generateUDTRecordTypeSetter(column, i, out); + + generateUDTRecordTypeGetter(column, i, out); + } + + generateUDTRecordTypeClassFooter(udt, out); + out.println("}"); + } + + /** + * Subclasses may override this method to provide their own record type setters. + */ + protected void generateUDTRecordTypeSetter(TypedElementDefinition column, int index, JavaWriter out) { + generateUDTRecordTypeSetter0(column, index, out); + } + + private final void generateUDTRecordTypeSetter0(TypedElementDefinition column, @SuppressWarnings("unused") int index, JavaWriter out) { + final String className = getStrategy().getJavaClassName(column.getContainer(), Mode.RECORD_TYPE); + final String setterReturnType = generateFluentSetters() ? className : tokenVoid; + final String setter = getStrategy().getJavaSetterName(column, Mode.RECORD_TYPE); + final String typeFull = getJavaType(column.getType(resolver(out, Mode.RECORD_TYPE)), out, Mode.RECORD_TYPE); + final String type = out.ref(typeFull); + final String name = column.getQualifiedOutputName(); + + if (!kotlin && !printDeprecationIfUnknownType(out, typeFull)) + out.javadoc("Setter for %s.[[before= ][%s]]", name, list(escapeEntities(comment(column)))); + + if (scala) + out.println("%sdef %s(value: %s): %s", visibilityPublic(), setter, type, setterReturnType); + // The property is already defined in the getter + else if (kotlin) {} + else + out.println("%s%s %s([[before=@][after= ][%s]]%s value);", visibilityPublic(), setterReturnType, setter, list(nullableOrNonnullAnnotation(out, column)), varargsIfArray(type)); + } + + /** + * Subclasses may override this method to provide their own record type getters. + */ + protected void generateUDTRecordTypeGetter(TypedElementDefinition column, int index, JavaWriter out) { + generateUDTRecordTypeGetter0(column, index, out); + } + + private final void generateUDTRecordTypeGetter0(TypedElementDefinition column, @SuppressWarnings("unused") int index, JavaWriter out) { + final String member = getStrategy().getJavaMemberName(column, Mode.RECORD_TYPE); + final String getter = getStrategy().getJavaGetterName(column, Mode.RECORD_TYPE); + final String typeFull = getJavaType(column.getType(resolver(out, Mode.RECORD_TYPE)), out, Mode.RECORD_TYPE); + final String type = out.ref(typeFull); + final String name = column.getQualifiedOutputName(); + + if (!kotlin && !printDeprecationIfUnknownType(out, typeFull)) + out.javadoc("Getter for %s.[[before= ][%s]]", name, list(escapeEntities(comment(column)))); + + printValidationAnnotation(out, column); + printNullableOrNonnullAnnotation(out, column); + if (kotlin && !generateImmutableInterfaces()) + printKotlinSetterAnnotation(out, column, Mode.RECORD_TYPE); + + if (scala) + out.println("%sdef %s: %s", visibilityPublic(), scalaWhitespaceSuffix(getter), type); + else if (kotlin) + out.println("%s%s %s: %s%s", visibilityPublic(), (generateImmutableInterfaces() ? "val" : "var"), member, type, kotlinNullability(out, column, Mode.RECORD_TYPE)); + else + out.println("%s%s %s();", visibilityPublic(), type, getter); + } + @FunctionalInterface private interface EmbeddableFilter { void accept(List result, Set duplicates, int index, EmbeddableDefinition embeddable); @@ -2584,13 +2693,13 @@ public class JavaGenerator extends AbstractGenerator { } private String getJavaType(Definition column, JavaWriter out) { - return getJavaType(column, out, Mode.RECORD); + return getJavaType(column, out, Mode.DEFAULT); } private String getJavaType(Definition column, JavaWriter out, Mode mode) { return column instanceof EmbeddableDefinition - ? getStrategy().getFullJavaClassName(column, mode) - : getJavaType(((TypedElementDefinition) column).getType(resolver(out)), out); + ? getStrategy().getFullJavaClassName(column, mode == Mode.DEFAULT ? Mode.RECORD : mode) + : getJavaType(((TypedElementDefinition) column).getType(resolver(out)), out, mode); } private String getJavaTypeRef(Definition column, JavaWriter out) { @@ -2677,7 +2786,7 @@ public class JavaGenerator extends AbstractGenerator { // [#3117] Avoid covariant setters for UDTs when generating interfaces if (generateInterfaces() && !generateImmutableInterfaces() && (isUDT || isArray)) { - final String columnTypeFull = getJavaType(column.getType(resolver(out, Mode.RECORD)), out, Mode.RECORD); + final String columnTypeFull = getJavaType(column.getType(resolver(out, Mode.DEFAULT)), out, Mode.DEFAULT); final String columnType = out.ref(columnTypeFull); final String columnTypeInterface = out.ref(getJavaType(column.getType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); @@ -3456,6 +3565,40 @@ public class JavaGenerator extends AbstractGenerator { } } + if (udt.getSupertype() != null) { + final String superUdtId = out.ref(getStrategy().getFullJavaIdentifier(udt.getSupertype()), 2); + + // [#644] TODO + if (scala) { + } + // [#644] TODO + else if (kotlin) { + } + else { + out.overrideInherit(); + out.println("%s%s getSupertype() {", visibilityPublic(), UDT.class); + out.println("return %s;", superUdtId); + out.println("}"); + } + } + + if (!udt.getSubtypes().isEmpty()) { + final List subUdtIds = out.ref(getStrategy().getFullJavaIdentifiers(udt.getSubtypes()), 2); + + // [#644] TODO + if (scala) { + } + // [#644] TODO + else if (kotlin) { + } + else { + out.overrideInherit(); + out.println("%s%s<%s> getSubtypes() {", visibilityPublic(), List.class, UDT.class); + out.println("return %s.asList([[%s]]);", Arrays.class, subUdtIds); + out.println("}"); + } + } + generateUDTClassFooter(udt, out); out.println("}"); closeJavaWriter(out); @@ -3708,6 +3851,41 @@ public class JavaGenerator extends AbstractGenerator { printClassJavadoc(out, udt, "The udt " + udt.getQualifiedInputName() + "."); } + /** + * Generating UDT record types + */ + protected void generateUDTRecordTypes(SchemaDefinition schema) { + log.info("Generating UDT record types"); + + for (UDTDefinition udt : database.getUDTs(schema)) { + try { + if (udt.getSupertype() != null || !udt.getSubtypes().isEmpty()) + generateUDTRecordType(udt); + } + catch (Exception e) { + log.error("Error while generating UDT record types " + udt, e); + } + } + + watch.splitInfo("UDT record types generated"); + } + + /** + * Subclasses may override this method to provide udt record class footer code. + */ + @SuppressWarnings("unused") + protected void generateUDTRecordTypeClassFooter(UDTDefinition udt, JavaWriter out) {} + + /** + * Subclasses may override this method to provide their own Javadoc. + */ + protected void generateUDTRecordTypeClassJavadoc(UDTDefinition udt, JavaWriter out) { + if (generateCommentsOnUDTs()) + printClassJavadoc(out, udt); + else + printClassJavadoc(out, udt, "The udt " + udt.getQualifiedInputName() + "."); + } + protected void generateUDTRoutines(SchemaDefinition schema) { for (UDTDefinition udt : database.getUDTs(schema)) { if (udt.getRoutines().size() > 0) { @@ -10702,7 +10880,7 @@ public class JavaGenerator extends AbstractGenerator { } protected String getJavaType(DataTypeDefinition type, JavaWriter out) { - return getJavaType(type, out, Mode.RECORD); + return getJavaType(type, out, Mode.DEFAULT); } protected String getJavaType(DataTypeDefinition type, JavaWriter out, Mode udtMode) { @@ -10731,7 +10909,7 @@ public class JavaGenerator extends AbstractGenerator { } protected String getType(Database db, SchemaDefinition schema, JavaWriter out, String t, int p, int s, Name u, String javaType, String defaultType) { - return getType(db, schema, out, t, p, s, u, javaType, defaultType, Mode.RECORD); + return getType(db, schema, out, t, p, s, u, javaType, defaultType, Mode.DEFAULT); } protected String getType(Database db, SchemaDefinition schema, JavaWriter out, String t, int p, int s, Name u, String javaType, String defaultType, Mode udtMode) { @@ -10739,7 +10917,9 @@ public class JavaGenerator extends AbstractGenerator { } protected String getType(Database db, SchemaDefinition schema, JavaWriter out, String t, int p, int s, Name u, String javaType, String defaultType, Mode udtMode, XMLTypeDefinition xmlType) { + UDTDefinition udt; String type = defaultType; + Mode udtDefaultMode = udtMode == Mode.DEFAULT ? Mode.RECORD : udtMode; // XML types if (xmlType != null) { @@ -10762,7 +10942,7 @@ public class JavaGenerator extends AbstractGenerator { // [#10309] TODO: The schema should be taken from baseType, if available. Might be different than the argument schema. // When can this happen? - String baseType = getType(db, schema, out, newT, p, s, bt.u(), javaType, defaultType, udtMode); + String baseType = getType(db, schema, out, newT, p, s, bt.u(), javaType, defaultType, udtDefaultMode); if (scala) type = "scala.Array[" + baseType + "]"; @@ -10804,14 +10984,17 @@ public class JavaGenerator extends AbstractGenerator { } // Check for UDTs - else if (db.getUDT(schema, u) != null) { - type = getStrategy().getFullJavaClassName(db.getUDT(schema, u), udtMode); + else if ((udt = db.getUDT(schema, u)) != null) { + if (udt.getSubtypes().isEmpty() || udtMode == Mode.RECORD || udtMode == Mode.POJO || udtMode == Mode.INTERFACE) + type = getStrategy().getFullJavaClassName(udt, udtDefaultMode); + else + type = getStrategy().getFullJavaClassName(udt, Mode.RECORD_TYPE) + ""; } // [#3942] [#7863] Dialects that support tables as UDTs // [#5334] In MySQL, the user type is (ab)used for synthetic enum types. This can lead to accidental matches here else if (SUPPORT_TABLE_AS_UDT.contains(db.getDialect()) && db.getTable(schema, u) != null) { - type = getStrategy().getFullJavaClassName(db.getTable(schema, u), udtMode); + type = getStrategy().getFullJavaClassName(db.getTable(schema, u), udtDefaultMode); } // Check for custom types diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java index fdbc13b860..2e1e960cd6 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java @@ -330,6 +330,7 @@ public abstract class AbstractDatabase implements Database { private transient Map> xmlSchemaCollectionsBySchema; private transient Map> udtsBySchema; private transient Map> udtsByPackage; + private transient Map> subtypesByUdt; private transient Map> arraysBySchema; private transient Map> routinesBySchema; private transient Map> packagesBySchema; @@ -3101,6 +3102,14 @@ public abstract class AbstractDatabase implements Database { return filterPackage(getUDTs(), pkg, udtsByPackage); } + @Override + public List getSubtypes(UDTDefinition udt) { + if (subtypesByUdt == null) + subtypesByUdt = new LinkedHashMap<>(); + + return filterSupertype(getUDTs(), udt, subtypesByUdt); + } + @Override public final Relations getRelations() { if (relations == null) { @@ -3304,6 +3313,23 @@ public abstract class AbstractDatabase implements Database { return result; } + final List filterSupertype(List definitions, UDTDefinition supertype, Map> cache) { + return cache.computeIfAbsent(supertype, u -> filterSupertype(definitions, u)); + } + + final List filterSupertype(List definitions, UDTDefinition u) { + if (u == null) + return definitions; + + List result = new ArrayList<>(); + + for (UDTDefinition definition : definitions) + if (definition.getSupertype() != null && definition.getSupertype().equals(u)) + result.add(definition); + + return result; + } + protected final List filterTable(List definitions, TableDefinition table, Map> cache) { List result = cache.get(table); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractUDTDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractUDTDefinition.java index a706456652..a562b2f799 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractUDTDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractUDTDefinition.java @@ -53,6 +53,8 @@ implements private List routines; private final boolean synthetic; + private SchemaDefinition supertypeSchema; + private String supertypeName; public AbstractUDTDefinition(SchemaDefinition schema, String name, String comment) { this(schema, null, name, false, comment); @@ -63,9 +65,15 @@ implements } public AbstractUDTDefinition(SchemaDefinition schema, PackageDefinition pkg, String name, boolean synthetic, String comment) { + this(schema, pkg, name, synthetic, comment, null, null); + } + + public AbstractUDTDefinition(SchemaDefinition schema, PackageDefinition pkg, String name, boolean synthetic, String comment, SchemaDefinition supertypeSchema, String supertypeName) { super(schema, pkg, name, comment); this.synthetic = synthetic; + this.supertypeSchema = supertypeSchema; + this.supertypeName = supertypeName; } @Override @@ -107,4 +115,14 @@ implements public boolean isSynthetic() { return synthetic; } + + @Override + public UDTDefinition getSupertype() { + return supertypeSchema == null ? null : getDatabase().getUDT(supertypeSchema, supertypeName); + } + + @Override + public List getSubtypes() { + return getDatabase().getSubtypes(this); + } } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/Database.java b/jOOQ-meta/src/main/java/org/jooq/meta/Database.java index 9af6d2228c..93d24c3595 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/Database.java @@ -411,6 +411,11 @@ public interface Database extends AutoCloseable { */ List getUDTs(PackageDefinition pkg); + /** + * Get the subtypes of a UDT, if any. + */ + List getSubtypes(UDTDefinition udt); + /** * The Arrays defined in this database. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/UDTDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/UDTDefinition.java index 35a4d498f1..09014aee70 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/UDTDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/UDTDefinition.java @@ -76,4 +76,14 @@ public interface UDTDefinition extends PackageDefinition { */ @Override boolean isSynthetic(); + + /** + * The subtypes of this UDT, if any. + */ + List getSubtypes(); + + /** + * The supertype of this UDT, if any. + */ + UDTDefinition getSupertype(); } diff --git a/jOOQ/src/main/java/org/jooq/UDT.java b/jOOQ/src/main/java/org/jooq/UDT.java index ec8da32b85..b1e66e7350 100644 --- a/jOOQ/src/main/java/org/jooq/UDT.java +++ b/jOOQ/src/main/java/org/jooq/UDT.java @@ -37,6 +37,11 @@ */ package org.jooq; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * UDT definition. *

@@ -63,4 +68,23 @@ public interface UDT> extends RecordQualifier { * */ boolean isSynthetic(); + + /** + * Get the supertype of this {@link UDT}, or null if this type + * has no supertype. + */ + @Nullable + UDT getSupertype(); + + /** + * Get the subtypes of this {@link UDT} or an empty list, if there are no + * known subtypes. + */ + @NotNull + List> getSubtypes(); + + /** + * Check if this type is a supertype or the same type as another {@link UDT} type. + */ + boolean isAssignableFrom(UDT other); } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java index ef7d33a58d..a3b202dcae 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java @@ -273,11 +273,11 @@ implements * @deprecated - 3.20.0 - [#15723] - Re-generate your code. */ @Deprecated - protected AbstractRoutine(String name, Schema schema, DataType type) { + protected AbstractRoutine(String name, Schema schema, DataType type) { this(name, schema, (Package) null, type, null, null); } - protected AbstractRoutine(String name, Schema schema, Comment comment, DataType type) { + protected AbstractRoutine(String name, Schema schema, Comment comment, DataType type) { this(name, schema, (Package) null, comment, type, null, null); } @@ -321,11 +321,11 @@ implements * @deprecated - 3.20.0 - [#15723] - Re-generate your code. */ @Deprecated - protected AbstractRoutine(String name, Schema schema, Package pkg, DataType type) { + protected AbstractRoutine(String name, Schema schema, Package pkg, DataType type) { this(name, schema, pkg, type, null, null); } - protected AbstractRoutine(String name, Schema schema, Package pkg, Comment comment, DataType type) { + protected AbstractRoutine(String name, Schema schema, Package pkg, Comment comment, DataType type) { this(name, schema, pkg, comment, type, null, null); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 26e250c07c..26b6639654 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -358,7 +358,7 @@ public class DefaultBinding implements Binding { } @SuppressWarnings({ "rawtypes", "unchecked" }) - static final Binding binding(DataType dataType, Converter converter) { + static final Binding binding(DataType dataType, Converter converter) { Class type = converter.fromType(); // Concrete types @@ -550,7 +550,7 @@ public class DefaultBinding implements Binding { } @SuppressWarnings({ "rawtypes", "unchecked" }) - static final Binding newBinding(final Converter converter, final DataType dataType, final Binding binding) { + static final Binding newBinding(final Converter converter, final DataType dataType, final Binding binding) { final Binding theBinding; @@ -622,7 +622,9 @@ public class DefaultBinding implements Binding { try { if (QualifiedRecord.class.isAssignableFrom(type)) { Class> t = (Class>) type; - result.put(getMappedUDTName(scope, t), t); + + // [#644] Prevent infinite recursion between fields and subtypes + if (result.putIfAbsent(getMappedUDTName(scope, t), t) == null) { @@ -635,8 +637,16 @@ public class DefaultBinding implements Binding { - for (Field field : getRecordQualifier(t).fields()) - typeMap(field.getType(), scope, result); + RecordQualifier q = getRecordQualifier(t); + for (Field field : q.fields()) + typeMap(field.getType(), scope, result); + + // [#644] Put subtypes into the type map as well + if (q instanceof UDT u) { + for (UDT s : u.getSubtypes()) + typeMap(s.getRecordType(), scope, result); + } + } } @@ -647,6 +657,7 @@ public class DefaultBinding implements Binding { + } catch (Exception e) { throw new MappingException("Error while collecting type map", e); @@ -1871,6 +1882,14 @@ public class DefaultBinding implements Binding { + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/Internal.java b/jOOQ/src/main/java/org/jooq/impl/Internal.java index 795d73b231..9fb27dd78e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Internal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Internal.java @@ -643,7 +643,7 @@ public final class Internal { * Factory method for parameters. */ @NotNull - public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed) { + public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed) { return createParameter(name, type, isDefaulted, isUnnamed, null, null); } @@ -651,7 +651,7 @@ public final class Internal { * Factory method for parameters. */ @NotNull - public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed, Converter converter) { + public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed, Converter converter) { return createParameter(name, type, isDefaulted, isUnnamed, converter, null); } @@ -659,7 +659,7 @@ public final class Internal { * Factory method for parameters. */ @NotNull - public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed, Binding binding) { + public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed, Binding binding) { return createParameter(name, type, isDefaulted, isUnnamed, null, binding); } @@ -668,7 +668,7 @@ public final class Internal { */ @NotNull @SuppressWarnings("unchecked") - public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed, Converter converter, Binding binding) { + public static final Parameter createParameter(String name, DataType type, boolean isDefaulted, boolean isUnnamed, Converter converter, Binding binding) { final Binding actualBinding = DefaultBinding.newBinding(converter, type, binding); final DataType actualType = converter == null && binding == null ? (DataType) type diff --git a/jOOQ/src/main/java/org/jooq/impl/UDTDataType.java b/jOOQ/src/main/java/org/jooq/impl/UDTDataType.java index 51beff0c65..326186ecdb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/UDTDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/UDTDataType.java @@ -37,6 +37,7 @@ */ package org.jooq.impl; +import org.jooq.ConverterContext; import org.jooq.Record; import org.jooq.Row; import org.jooq.SQLDialect; diff --git a/jOOQ/src/main/java/org/jooq/impl/UDTImpl.java b/jOOQ/src/main/java/org/jooq/impl/UDTImpl.java index ab99865dca..c4ecd68bac 100644 --- a/jOOQ/src/main/java/org/jooq/impl/UDTImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/UDTImpl.java @@ -37,6 +37,12 @@ */ package org.jooq.impl; +import static java.util.Collections.emptyList; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + import org.jooq.Binding; import org.jooq.Catalog; import org.jooq.Comment; @@ -54,6 +60,7 @@ import org.jooq.UDTField; import org.jooq.UDTRecord; import org.jooq.impl.QOM.UNotYetImplemented; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.ApiStatus.Internal; /** @@ -163,6 +170,25 @@ implements throw new UnsupportedOperationException(); } + @Override + public /* non-final */ UDT getSupertype() { + return null; + } + + @Override + public /* non-final */ List> getSubtypes() { + return emptyList(); + } + + @Override + public final boolean isAssignableFrom(UDT other) { + if (equals(other)) + return true; + + UDT s = other.getSupertype(); + return s != null ? isAssignableFrom(s) : false; + } + @Override public final boolean isSQLUsable() { return true ; @@ -209,7 +235,7 @@ implements * instead. */ @Deprecated - protected static final , T> UDTField createField(String name, DataType type, UDT udt) { + protected static final , T> UDTField createField(String name, DataType type, UDT udt) { return createField(DSL.name(name), type, udt, "", null, null); } @@ -224,7 +250,7 @@ implements * instead. */ @Deprecated - protected static final , T> UDTField createField(String name, DataType type, UDT udt, String comment) { + protected static final , T> UDTField createField(String name, DataType type, UDT udt, String comment) { return createField(DSL.name(name), type, udt, comment, null, null); } @@ -239,7 +265,7 @@ implements * instead. */ @Deprecated - protected static final , T, U> UDTField createField(String name, DataType type, UDT udt, String comment, Converter converter) { + protected static final , T, U> UDTField createField(String name, DataType type, UDT udt, String comment, Converter converter) { return createField(DSL.name(name), type, udt, comment, converter, null); } @@ -254,7 +280,7 @@ implements * instead. */ @Deprecated - protected static final , T, U> UDTField createField(String name, DataType type, UDT udt, String comment, Binding binding) { + protected static final , T, U> UDTField createField(String name, DataType type, UDT udt, String comment, Binding binding) { return createField(DSL.name(name), type, udt, comment, null, binding); } @@ -269,7 +295,7 @@ implements * instead. */ @Deprecated - protected static final , T, X, U> UDTField createField(String name, DataType type, UDT udt, String comment, Converter converter, Binding binding) { + protected static final , T, X, U> UDTField createField(String name, DataType type, UDT udt, String comment, Converter converter, Binding binding) { return createField(DSL.name(name), type, udt, comment, converter, binding); } @@ -280,7 +306,7 @@ implements * @param name The name of the field (case-sensitive!) * @param type The data type of the field */ - protected static final , T> UDTField createField(Name name, DataType type, UDT udt) { + protected static final , T> UDTField createField(Name name, DataType type, UDT udt) { return createField(name, type, udt, "", null, null); } @@ -291,7 +317,7 @@ implements * @param name The name of the field (case-sensitive!) * @param type The data type of the field */ - protected static final , T> UDTField createField(Name name, DataType type, UDT udt, String comment) { + protected static final , T> UDTField createField(Name name, DataType type, UDT udt, String comment) { return createField(name, type, udt, comment, null, null); } @@ -302,7 +328,7 @@ implements * @param name The name of the field (case-sensitive!) * @param type The data type of the field */ - protected static final , T, U> UDTField createField(Name name, DataType type, UDT udt, String comment, Converter converter) { + protected static final , T, U> UDTField createField(Name name, DataType type, UDT udt, String comment, Converter converter) { return createField(name, type, udt, comment, converter, null); } @@ -313,7 +339,7 @@ implements * @param name The name of the field (case-sensitive!) * @param type The data type of the field */ - protected static final , T, U> UDTField createField(Name name, DataType type, UDT udt, String comment, Binding binding) { + protected static final , T, U> UDTField createField(Name name, DataType type, UDT udt, String comment, Binding binding) { return createField(name, type, udt, comment, null, binding); } @@ -325,7 +351,7 @@ implements * @param type The data type of the field */ @SuppressWarnings("unchecked") - protected static final , T, X, U> UDTField createField(Name name, DataType type, UDT udt, String comment, Converter converter, Binding binding) { + protected static final , T, X, U> UDTField createField(Name name, DataType type, UDT udt, String comment, Converter converter, Binding binding) { final Binding actualBinding = DefaultBinding.newBinding(converter, type, binding); final DataType actualType = converter == null && binding == null ? (DataType) type