From 5094618d5271ab401a9f573b2fbc35db0d9ba4ed Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 18 Nov 2025 16:39:57 +0100 Subject: [PATCH] [jOOQ/jOOQ#19247] Compilation error in generated Record POJO constructors when table uses a table type reference in PostgreSQL --- .../java/org/jooq/codegen/JavaGenerator.java | 95 ++++++++++--------- .../org/jooq/meta/DataTypeDefinition.java | 11 +++ .../jooq/meta/DefaultDataTypeDefinition.java | 14 +++ 3 files changed, 76 insertions(+), 44 deletions(-) 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 83d1961b71..72ecfea1be 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -2613,18 +2613,18 @@ public class JavaGenerator extends AbstractGenerator { final TypedElementDefinition t = (TypedElementDefinition) column; final JavaTypeResolver r = resolver(out, Mode.RECORD); - final boolean isUDT = t.getType(r).isUDT(); + final boolean isUDTOrTable = t.getType(r).isUDT() || t.getType(r).isTable(); final boolean isArray = t.getType(r).isArray(); - final boolean isUDTArray = t.getType(r).isUDTArray(); + final boolean isUDTOrTableArray = t.getType(r).isUDTArray() || t.getType(r).isTableArray(); final ArrayDefinition array = database.getArray(t.getType(r).getSchema(), t.getType(r).getQualifiedUserType()); final String indexTypeFull = array == null || array.getIndexType() == null ? null : getJavaType(array.getIndexType(resolver(out)), out); final boolean isArrayOfUDTs = isArrayOfUDTs(t, r, Mode.RECORD); final boolean isConverted = t.getType(r).getConverter() != null || t.getType(r).getBinding() != null; - final String udtType = (isUDT || isArray) + final String udtType = (isUDTOrTable || isArray) ? out.ref(getJavaType(t.getType(r), out, Mode.RECORD)) : ""; - final String udtArrayElementType = isUDTArray + final String udtArrayElementType = isUDTOrTableArray ? out.ref(database.getArray(t.getType(r).getSchema(), t.getType(r).getQualifiedUserType()).getElementType(r).getJavaType(r)) : isArrayOfUDTs ? out.ref(getArrayBaseType(t.getType(r).getJavaType(r))) @@ -2632,7 +2632,7 @@ public class JavaGenerator extends AbstractGenerator { if (kotlin) { if (pojoArgument) - if (isUDTArray) + if (isUDTOrTableArray) out.println("this.%s = value.%s?.let { %s(it.map { it?.let { %s(it) } }) }", getStrategy().getJavaMemberName(column, Mode.POJO), getStrategy().getJavaMemberName(column, Mode.POJO), @@ -2643,7 +2643,7 @@ public class JavaGenerator extends AbstractGenerator { getStrategy().getJavaMemberName(column, Mode.POJO), getStrategy().getJavaMemberName(column, Mode.POJO), udtArrayElementType); - else if (isUDT && !isConverted || isArray) + else if (isUDTOrTable && !isConverted || isArray) out.println("this.%s = value.%s?.let { %s(it) }", getStrategy().getJavaMemberName(column, Mode.POJO), getStrategy().getJavaMemberName(column, Mode.POJO), @@ -2665,7 +2665,7 @@ public class JavaGenerator extends AbstractGenerator { // In Scala, the setter call can be ambiguous, e.g. when using KeepNamesGeneratorStrategy else if (scala) { if (pojoArgument) - if (isUDTArray) + if (isUDTOrTableArray) out.println("this.%s(if (value.%s == null) null else new %s(value.%s.stream().map { it => new %s(it) }.collect(%s.toList())))", getStrategy().getJavaSetterName(column, Mode.POJO), getStrategy().getJavaGetterName(column, Mode.POJO), @@ -2679,7 +2679,7 @@ public class JavaGenerator extends AbstractGenerator { getStrategy().getJavaGetterName(column, Mode.POJO), getStrategy().getJavaGetterName(column, Mode.POJO), udtArrayElementType); - else if (isUDT && !isConverted || isArray) + else if (isUDTOrTable && !isConverted || isArray) out.println("this.%s(if (value.%s == null) null else new %s(value.%s))", getStrategy().getJavaSetterName(column, Mode.RECORD), getStrategy().getJavaGetterName(column, Mode.POJO), @@ -2702,7 +2702,7 @@ public class JavaGenerator extends AbstractGenerator { ? getStrategy().getJavaMemberName(column, Mode.POJO) : getStrategy().getJavaGetterName(column, Mode.POJO); - if (isUDTArray) { + if (isUDTOrTableArray) { if (indexTypeFull == null) { out.println("%s(value.%s() == null ? null : new %s(value.%s().stream().map(%s::new).collect(%s.toList())));", getStrategy().getJavaSetterName(column, Mode.RECORD), @@ -2741,7 +2741,7 @@ public class JavaGenerator extends AbstractGenerator { brackets ); } - else if (isUDT && !isConverted || isArray) { + else if (isUDTOrTable && !isConverted || isArray) { out.println("%s(value.%s() == null ? null : new %s(value.%s()));", getStrategy().getJavaSetterName(column, Mode.RECORD), getterName, @@ -2842,15 +2842,15 @@ public class JavaGenerator extends AbstractGenerator { final String typeFull = getJavaType(column.getType(resolver(out)), out); final String type = out.ref(typeFull); final String name = column.getQualifiedOutputName(); - final boolean isUDT = column.getType(resolver(out)).isUDT(); + final boolean isUDTOrTable = column.getType(resolver(out)).isUDT() || column.getType(resolver(out)).isTable(); final boolean isArray = column.getType(resolver(out)).isArray(); - final boolean override = generateInterfaces() && !generateImmutableInterfaces() && !isUDT + final boolean override = generateInterfaces() && !generateImmutableInterfaces() && !isUDTOrTable || column instanceof AttributeDefinition && ((AttributeDefinition) column).getContainer().isInTypeHierarchy() || !kotlin && getStrategy().getJavaSetterOverride(column, Mode.RECORD) || kotlin && getStrategy().getJavaMemberOverride(column, Mode.RECORD); // We cannot have covariant setters for arrays because of type erasure - if (!(generateInterfaces() && (isArray || isUDT))) { + if (!(generateInterfaces() && (isArray || isUDTOrTable))) { if (!kotlin && !printDeprecationIfUnknownType(out, typeFull)) out.javadoc("Setter for %s.[[before= ][%s]]", name, list(escapeEntities(comment(column)))); @@ -2890,7 +2890,7 @@ public class JavaGenerator extends AbstractGenerator { } // [#3117] Avoid covariant setters for UDTs when generating interfaces - if (generateInterfaces() && (isUDT || isArray)) { + if (generateInterfaces() && (isUDTOrTable || isArray)) { final String columnTypeFull = getJavaType(column.getType(resolver(out, Mode.DEFAULT)), out, Mode.DEFAULT); final String columnTypeInterface = out.ref(getJavaType(column.getType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); final UDTDefinition udt = column.getDatabase().getUDT(column.getSchema(), column.getType().getUserType()); @@ -2898,7 +2898,7 @@ public class JavaGenerator extends AbstractGenerator { if (!printDeprecationIfUnknownType(out, columnTypeFull)) out.javadoc("Setter for %s.[[before= ][%s]]", name, list(escapeEntities(comment(column)))); - if (!generateImmutableInterfaces() || udt.isInTypeHierarchy()) + if (!generateImmutableInterfaces() || udt != null && udt.isInTypeHierarchy()) out.override(); if (scala) { @@ -2938,12 +2938,12 @@ public class JavaGenerator extends AbstractGenerator { final String typeFull = getJavaType(column.getType(resolver(out)), out); final String type = out.ref(typeFull); final String recordTypeFull = getJavaType(column.getType(resolver(out, Mode.RECORD)), out, Mode.RECORD); - final boolean isUDT = column.getType(resolver(out)).isUDT(); - final boolean isUDTArray = column.getType(resolver(out)).isUDTArray(); + final boolean isUDTOrTable = column.getType(resolver(out)).isUDT() || column.getType(resolver(out)).isTable(); + final boolean isUDTOrTableArray = column.getType(resolver(out)).isUDTArray() || column.getType(resolver(out)).isTableArray(); final boolean isArray = column.getType(resolver(out)).isArray(); final boolean isArrayOfUDTs = isArrayOfUDTs(column, resolver(out, RECORD), RECORD); - if (generateInterfaces() && (isUDT || isArray)) { + if (generateInterfaces() && (isUDTOrTable || isArray)) { final String typeInterface = out.ref(getJavaType(column.getType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); final UDTDefinition udt = column.getDatabase().getUDT(column.getSchema(), column.getType().getUserType()); @@ -2956,7 +2956,7 @@ public class JavaGenerator extends AbstractGenerator { out.println("else if (%s.isInstanceOf[%s])", member, type); out.println("return %s.asInstanceOf[%s]", member, type); - if (isUDT && udt != null) + if (isUDTOrTable && udt != null) generateRecordSetterSubtypeChecks0(out, index, column, udt); } @@ -2968,7 +2968,7 @@ public class JavaGenerator extends AbstractGenerator { out.println("return %s.map(u => new %s(%s))", member, columnBaseTypeName, udtInterfaceGetterCalls(udt, "u")); } else - out.println("return new %s(%s)", out.ref(recordTypeFull), udtInterfaceGetterCalls(udt, member)); + out.println("return new %s(%s)", out.ref(recordTypeFull), udt != null ? udtInterfaceGetterCalls(udt, member) : member); out.println("}"); } @@ -2981,7 +2981,7 @@ public class JavaGenerator extends AbstractGenerator { out.println("if (%s == null)", member); out.println("return null;"); - if (isUDT) { + if (isUDTOrTable) { if (!isArrayOfUDTs) { out.println("else if (%s instanceof %s)", member, type); out.println("return (%s) %s;", type, member); @@ -2998,7 +2998,7 @@ public class JavaGenerator extends AbstractGenerator { out.println("return %s.of(%s).map(u -> new %s(%s)).toArray(%s[]::new);", Stream.class, member, columnBaseTypeName, udtInterfaceGetterCalls(udt, "u"), columnBaseTypeName); } else - out.println("return new %s(%s);", out.ref(recordTypeFull), udtInterfaceGetterCalls(udt, member)); + out.println("return new %s(%s);", out.ref(recordTypeFull), udt != null ? udtInterfaceGetterCalls(udt, member) : member); } else if (isArray) { final ArrayDefinition array = database.getArray(column.getType(resolver(out)).getSchema(), column.getType(resolver(out)).getQualifiedUserType()); @@ -3010,7 +3010,7 @@ public class JavaGenerator extends AbstractGenerator { out.println(); out.println("for (%s i : %s)", componentTypeInterface, member); - if (isUDTArray) + if (isUDTOrTableArray) out.println("a.add(i.into(new %s()));", componentType); else out.println("a.add(i);", componentType); @@ -6167,10 +6167,10 @@ public class JavaGenerator extends AbstractGenerator { final Definition column = embeddablesAndUnreplacedColumns.get(i); if (column instanceof TypedElementDefinition t) { - final boolean isUDT = t.getType(resolver(out)).isUDT(); - final boolean isUDTArray = t.getType(resolver(out)).isUDTArray(); + final boolean isUDTOrTable = t.getType(resolver(out)).isUDT() || t.getType(resolver(out)).isTable(); + final boolean isUDTOrTableArray = t.getType(resolver(out)).isUDTArray() || t.getType(resolver(out)).isTableArray(); - if (isUDT || isUDTArray) { + if (isUDTOrTable || isUDTOrTableArray) { if (!hasUdts) { hasUdts = true; out.println(); @@ -6278,6 +6278,8 @@ public class JavaGenerator extends AbstractGenerator { if (column instanceof TypedElementDefinition e) { if (e.getType().isUDT()) return e.getDatabase().getUDT(e.getSchema(), e.getType().getQualifiedUserType()); + else if (e.getType().isTable()) + return e.getDatabase().getTable(e.getSchema(), e.getType().getQualifiedUserType()); } @@ -6692,13 +6694,14 @@ public class JavaGenerator extends AbstractGenerator { final String columnTypeFull = getJavaType(column.getType(resolver(out, Mode.POJO)), out, Mode.POJO); final String columnType = out.ref(columnTypeFull); final String columnMember = getStrategy().getJavaMemberName(column, Mode.POJO); - final boolean isUDT = column.getType(resolver(out)).isUDT(); - final boolean isUDTArray = column.getType(resolver(out)).isUDTArray(); + final boolean isUDTOrTable = column.getType(resolver(out)).isUDT() || column.getType(resolver(out)).isTable(); + final boolean isUDTOrTableArray = column.getType(resolver(out)).isUDTArray() || column.getType(resolver(out)).isTableArray(); final boolean isArrayOfUDTs = isArrayOfUDTs(column, resolver(out, POJO), POJO); - if (generateInterfaces() && (isUDT || isUDTArray)) { + if (generateInterfaces() && (isUDTOrTable || isUDTOrTableArray)) { final String columnTypeInterface = out.ref(getJavaType(column.getType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); final UDTDefinition udt = column.getDatabase().getUDT(column.getSchema(), column.getType().getUserType()); + final Definition udtOrTable = udt != null ? udt : column.getDatabase().getTable(column.getSchema(), column.getType().getUserType()); if (scala) { out.println(); @@ -6706,13 +6709,13 @@ public class JavaGenerator extends AbstractGenerator { out.println("if (%s == null)", columnMember); out.println("return null"); - if (isUDT && supportPojoInheritance()) + if (isUDTOrTable && supportPojoInheritance() && udt != null) generatePojoSetterSubtypeChecks0(out, index, column, udt); out.println("else"); if (isArrayOfUDTs) { - final String columnBaseTypeName = out.ref(getStrategy().getFullJavaClassName(udt, Mode.POJO)); + final String columnBaseTypeName = out.ref(getStrategy().getFullJavaClassName(udtOrTable, Mode.POJO)); out.println("return %s.map(u => new %s(u))", columnMember, columnBaseTypeName); } @@ -6733,21 +6736,21 @@ public class JavaGenerator extends AbstractGenerator { out.println("if (%s == null)", columnMember); out.println("return null;"); - if (isUDT) { - if (supportPojoInheritance()) + if (isUDTOrTable) { + if (supportPojoInheritance() && udt != null) generatePojoSetterSubtypeChecks0(out, index, column, udt); out.println("else"); if (isArrayOfUDTs) { - final String columnBaseTypeName = out.ref(getStrategy().getFullJavaClassName(udt, Mode.POJO)); + final String columnBaseTypeName = out.ref(getStrategy().getFullJavaClassName(udtOrTable, Mode.POJO)); out.println("return %s.of(%s).map(u -> new %s(%s)).toArray(%s[]::new);", Stream.class, columnMember, columnBaseTypeName, udtInterfaceGetterCalls(udt, "u"), columnBaseTypeName); } else - out.println("return new %s(%s);", columnType, udtInterfaceGetterCalls(udt, columnMember)); + out.println("return new %s(%s);", columnType, udt != null ? udtInterfaceGetterCalls(udt, columnMember) : columnMember); } - else if (isUDTArray) { + else if (isUDTOrTableArray) { final ArrayDefinition array = database.getArray(column.getType(resolver(out)).getSchema(), column.getType(resolver(out)).getQualifiedUserType()); final String componentType = out.ref(getJavaType(array.getElementType(resolver(out, Mode.POJO)), out, Mode.POJO)); final String componentTypeInterface = out.ref(getJavaType(array.getElementType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); @@ -6781,13 +6784,13 @@ public class JavaGenerator extends AbstractGenerator { final String columnSetterReturnType = generateFluentSetters() ? className : tokenVoid; final String columnSetter = getStrategy().getJavaSetterName(column, Mode.POJO); final String columnMember = getStrategy().getJavaMemberName(column, Mode.POJO); - final boolean isUDT = column.getType(resolver(out)).isUDT(); + final boolean isUDTOrTable = column.getType(resolver(out)).isUDT() || column.getType(resolver(out)).isTable(); final boolean isArray = column.getType(resolver(out)).isArray(); final String name = column.getQualifiedOutputName(); - final boolean override = generateInterfaces() && !generateImmutableInterfaces() && !isUDT; + final boolean override = generateInterfaces() && !generateImmutableInterfaces() && !isUDTOrTable; // We cannot have covariant setters for arrays because of type erasure - if (!(generateInterfaces() && (isArray || isUDT))) { + if (!(generateInterfaces() && (isArray || isUDTOrTable))) { if (!printDeprecationIfUnknownType(out, columnTypeFull)) out.javadoc("Setter for %s.[[before= ][%s]]", name, list(escapeEntities(comment(column)))); @@ -6815,7 +6818,7 @@ public class JavaGenerator extends AbstractGenerator { } // [#3117] To avoid covariant setters on POJOs, we need to generate two setter overloads - if (generateInterfaces() && (isUDT || isArray)) { + if (generateInterfaces() && (isUDTOrTable || isArray)) { final String columnTypeInterface = out.ref(getJavaType(column.getType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); out.println(); @@ -11032,12 +11035,14 @@ public class JavaGenerator extends AbstractGenerator { final String getter = parameter == procedure.getReturnValue() ? "getReturnValue" : getStrategy().getJavaGetterName(parameter, Mode.DEFAULT); - final boolean isUDT = parameter.getType(resolver(out)).isUDT(); + final boolean isUDTOrTable = + parameter.getType(resolver(out)).isUDT() + || parameter.getType(resolver(out)).isTable(); if (instance) { // [#3117] Avoid funny call-site ambiguity if this is a UDT that is implemented by an interface - if (generateInterfaces() && isUDT) { + if (generateInterfaces() && isUDTOrTable) { final String columnTypeInterface = out.ref(getJavaType(parameter.getType(resolver(out, Mode.INTERFACE)), out, Mode.INTERFACE)); if (scala) @@ -11792,9 +11797,11 @@ public class JavaGenerator extends AbstractGenerator { // Check for Oracle-style VARRAY types else if (db.getArray(schema, u) != null) { - boolean udtArray = db.getArray(schema, u).getElementType(resolver(out)).isUDT(); + boolean udtOrTableArray = + db.getArray(schema, u).getElementType(resolver(out)).isUDT() + || db.getArray(schema, u).getElementType(resolver(out)).isTable(); - if (udtMode == Mode.POJO || (udtMode == Mode.INTERFACE && !udtArray)) { + if (udtMode == Mode.POJO || (udtMode == Mode.INTERFACE && !udtOrTableArray)) { if (scala) type = "java.util.List[" + getJavaType(db.getArray(schema, u).getElementType(resolver(out, udtMode)), out, udtMode) + "]"; else diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DataTypeDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DataTypeDefinition.java index 2f552d6998..d812ffbd6b 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DataTypeDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DataTypeDefinition.java @@ -178,6 +178,11 @@ public interface DataTypeDefinition { */ boolean isUDT(); + /** + * Whether this data type represents a table type. + */ + boolean isTable(); + /** * Whether this data type represents an array producing an * {@link ArrayRecord}. @@ -190,6 +195,12 @@ public interface DataTypeDefinition { */ boolean isUDTArray(); + /** + * Whether this data type represents an array producing an + * {@link ArrayRecord} of table types. + */ + boolean isTableArray(); + /** * Whether this data type is a NUMBER type without precision and scale. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultDataTypeDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultDataTypeDefinition.java index 8f549c3c60..09a1791074 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultDataTypeDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultDataTypeDefinition.java @@ -297,6 +297,15 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition { return getDatabase().getUDT(schema, userType) != null; } + @Override + public final boolean isTable() { + if (userType == null) + return false; + + // [#19247] In PostgreSQL, tables expose types that can be used as UDTs as well in SQL + return getDatabase().getTable(schema, userType) != null; + } + @Override public final boolean isArray() { if (userType == null) @@ -310,6 +319,11 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition { return isArray() && getDatabase().getArray(schema, userType).getElementType(new DefaultJavaTypeResolver()).isUDT(); } + @Override + public final boolean isTableArray() { + return isArray() && getDatabase().getArray(schema, userType).getElementType(new DefaultJavaTypeResolver()).isTable(); + } + @Override public final String getType() { return type;