From 29bce7c90824fd93164e887ff97a56d083bd90f4 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 12 Aug 2020 17:11:36 +0200 Subject: [PATCH] [jOOQ/jOOQ#2530] [jOOQ/jOOQ#6124] [jOOQ/jOOQ#10481] Improved embeddable support and added code generation support for embeddable keys --- .../org/jooq/codegen/AbstractGenerator.java | 11 - .../codegen/DefaultGeneratorStrategy.java | 4 + .../java/org/jooq/codegen/GenerationTool.java | 4 +- .../main/java/org/jooq/codegen/Generator.java | 10 - .../java/org/jooq/codegen/JavaGenerator.java | 286 ++++++++++++------ .../java/org/jooq/meta/AbstractDatabase.java | 263 +++++++++++++--- .../jooq/meta/AbstractTableDefinition.java | 5 + .../java/org/jooq/meta/ColumnDefinition.java | 20 ++ .../src/main/java/org/jooq/meta/Database.java | 44 ++- .../jooq/meta/DefaultColumnDefinition.java | 41 ++- .../meta/DefaultEmbeddableDefinition.java | 73 ++++- .../meta/DefaultForeignKeyDefinition.java | 11 +- .../jooq/meta/DefaultUniqueKeyDefinition.java | 29 ++ .../org/jooq/meta/EmbeddableDefinition.java | 41 ++- .../org/jooq/meta/ForeignKeyDefinition.java | 10 + .../java/org/jooq/meta/TableDefinition.java | 7 +- .../org/jooq/meta/UniqueKeyDefinition.java | 10 + .../java/org/jooq/meta/jaxb/Database.java | 84 +++++ .../java/org/jooq/meta/jaxb/Embeddable.java | 86 +++++- .../java/org/jooq/meta/jaxb/Generate.java | 42 --- .../resources/xsd/jooq-codegen-3.14.0.xsd | 22 +- .../org/jooq/impl/EmbeddableRecordImpl.java | 5 + .../org/jooq/impl/EmbeddableTableField.java | 8 +- .../src/main/java/org/jooq/impl/Internal.java | 37 ++- 24 files changed, 923 insertions(+), 230 deletions(-) diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java index 26801c9f85..001db3b22e 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java @@ -142,7 +142,6 @@ abstract class AbstractGenerator implements Generator { boolean generateTableValuedFunctions = false; boolean generateEmptyCatalogs = false; boolean generateEmptySchemas = false; - boolean generatePrimaryKeyTypes = false; String generateNewline = "\n"; String generateIndentation; @@ -1077,16 +1076,6 @@ abstract class AbstractGenerator implements Generator { this.generateEmptySchemas = generateEmptySchemas; } - @Override - public boolean generatePrimaryKeyTypes() { - return generatePrimaryKeyTypes; - } - - @Override - public void setGeneratePrimaryKeyTypes(boolean generatePrimaryKeyTypes) { - this.generatePrimaryKeyTypes = generatePrimaryKeyTypes; - } - @Override public String generateNewline() { return generateNewline; 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 e51c610813..55d67b9fc1 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java @@ -156,6 +156,10 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { else if (definition instanceof ForeignKeyDefinition && asList(POSTGRES).contains(definition.getDatabase().getDialect().family())) return ((ForeignKeyDefinition) definition).getTable().getOutputName().toUpperCase() + "__" + definition.getOutputName().toUpperCase(); + // [#10481] Embeddables have a defining name (class name) and a referencing name (identifier name). + else if (definition instanceof EmbeddableDefinition) + return ((EmbeddableDefinition) definition).getReferencingOutputName().toUpperCase(); + else return definition.getOutputName().toUpperCase(); } diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java index 34bb217c73..a4e21e5a4e 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java @@ -546,6 +546,8 @@ public class GenerationTool { database.setConfiguredEnumTypes(d.getEnumTypes()); database.setConfiguredForcedTypes(d.getForcedTypes()); database.setConfiguredEmbeddables(d.getEmbeddables()); + database.setEmbeddablePrimaryKeys(TRUE.equals(g.getDatabase().isEmbeddablePrimaryKeys())); + database.setEmbeddableUniqueKeys(TRUE.equals(g.getDatabase().isEmbeddableUniqueKeys())); database.setLogSlowQueriesAfterSeconds(defaultIfNull(g.getDatabase().getLogSlowQueriesAfterSeconds(), 5)); database.setLogSlowResultsAfterSeconds(defaultIfNull(g.getDatabase().getLogSlowResultsAfterSeconds(), 5)); @@ -806,8 +808,6 @@ public class GenerationTool { generator.setGenerateEmptyCatalogs(g.getGenerate().isEmptyCatalogs()); if (g.getGenerate().isEmptySchemas() != null) generator.setGenerateEmptySchemas(g.getGenerate().isEmptySchemas()); - if (g.getGenerate().isPrimaryKeyTypes() != null) - generator.setGeneratePrimaryKeyTypes(g.getGenerate().isPrimaryKeyTypes()); if (g.getGenerate().getNewline() != null) generator.setGenerateNewline(g.getGenerate().getNewline()); if (g.getGenerate().getIndentation() != null) diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java index fb0ea4ae12..a7bf2b8088 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java @@ -959,16 +959,6 @@ public interface Generator { */ void setGenerateEmptySchemas(boolean generateEmptySchemas); - /** - * Whether wrapper types for primary keys should be generated. - */ - boolean generatePrimaryKeyTypes(); - - /** - * Whether wrapper types for primary keys should be generated. - */ - void setGeneratePrimaryKeyTypes(boolean generatePrimaryKeyTypes); - /** * The newline character(s) to be used in generated code. */ 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 10fc9d2929..28f62d5f72 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -96,6 +96,7 @@ import org.jooq.Index; import org.jooq.Name; import org.jooq.OrderField; import org.jooq.Parameter; +// ... import org.jooq.Record; import org.jooq.Result; import org.jooq.Row; @@ -1036,7 +1037,44 @@ public class JavaGenerator extends AbstractGenerator { out.println(";"); } + + + + + + + + + + + + + + + + + + + + + + + + + private void printCreateUniqueKey(JavaWriter out, UniqueKeyDefinition uniqueKey) { + + + + + + + + + printCreateNonEmbeddableUniqueKey(out, uniqueKey); + } + + private void printCreateNonEmbeddableUniqueKey(JavaWriter out, UniqueKeyDefinition uniqueKey) { if (scala) out.print("%s.createUniqueKey(%s, %s.name(\"%s\"), Array([[%s]]).asInstanceOf[Array[%s[%s, _] ] ], %s)", Internal.class, @@ -1066,6 +1104,21 @@ public class JavaGenerator extends AbstractGenerator { uniqueKey.enforced()); } + + + + + + + + + + + + + + + protected void printForeignKey(JavaWriter out, int foreignKeyCounter, ForeignKeyDefinition foreignKey) { final int block = foreignKeyCounter / INITIALISER_SIZE; @@ -1083,11 +1136,47 @@ public class JavaGenerator extends AbstractGenerator { } if (scala) - out.println("val %s: %s[%s, %s] = %s.createForeignKey(%s, %s.name(\"%s\"), Array([[%s]]).asInstanceOf[Array[%s[%s, _] ] ], %s, Array([[%s]]).asInstanceOf[Array[%s[%s, _] ] ], %s)", + out.print("val %s: %s[%s, %s] = ", getStrategy().getJavaIdentifier(foreignKey), + ForeignKey.class, + out.ref(getStrategy().getFullJavaClassName(foreignKey.getKeyTable(), Mode.RECORD)), + out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencedTable(), Mode.RECORD))); + else if (kotlin) + out.print("val %s: %s<%s, %s> = ", + getStrategy().getJavaIdentifier(foreignKey), + ForeignKey.class, + out.ref(getStrategy().getFullJavaClassName(foreignKey.getKeyTable(), Mode.RECORD)), + out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencedTable(), Mode.RECORD))); + else + out.print("static final %s<%s, %s> %s = ", ForeignKey.class, out.ref(getStrategy().getFullJavaClassName(foreignKey.getKeyTable(), Mode.RECORD)), out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencedTable(), Mode.RECORD)), + getStrategy().getJavaIdentifier(foreignKey)); + + + + + + + + + + + + + + printCreateNonEmbeddableForeignKey(out, foreignKey); + + if (scala || kotlin) + out.println(); + else + out.println(";"); + } + + private void printCreateNonEmbeddableForeignKey(JavaWriter out, ForeignKeyDefinition foreignKey) { + if (scala) + out.print("%s.createForeignKey(%s, %s.name(\"%s\"), Array([[%s]]).asInstanceOf[Array[%s[%s, _] ] ], %s, Array([[%s]]).asInstanceOf[Array[%s[%s, _] ] ], %s)", Internal.class, out.ref(getStrategy().getFullJavaIdentifier(foreignKey.getKeyTable()), 2), DSL.class, @@ -1102,11 +1191,7 @@ public class JavaGenerator extends AbstractGenerator { foreignKey.enforced() ); else if (kotlin) - out.println("val %s: %s<%s, %s> = %s.createForeignKey(%s, %s.name(\"%s\"), arrayOf([[%s]]), %s, arrayOf([[%s]]), %s)", - getStrategy().getJavaIdentifier(foreignKey), - ForeignKey.class, - out.ref(getStrategy().getFullJavaClassName(foreignKey.getKeyTable(), Mode.RECORD)), - out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencedTable(), Mode.RECORD)), + out.print("%s.createForeignKey(%s, %s.name(\"%s\"), arrayOf([[%s]]), %s, arrayOf([[%s]]), %s)", Internal.class, out.ref(getStrategy().getFullJavaIdentifier(foreignKey.getKeyTable()), 2), DSL.class, @@ -1117,11 +1202,7 @@ public class JavaGenerator extends AbstractGenerator { foreignKey.enforced() ); else - out.println("static final %s<%s, %s> %s = %s.createForeignKey(%s, %s.name(\"%s\"), new %s[] { [[%s]] }, %s, new %s[] { [[%s]] }, %s);", - ForeignKey.class, - out.ref(getStrategy().getFullJavaClassName(foreignKey.getKeyTable(), Mode.RECORD)), - out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencedTable(), Mode.RECORD)), - getStrategy().getJavaIdentifier(foreignKey), + out.print("%s.createForeignKey(%s, %s.name(\"%s\"), new %s[] { [[%s]] }, %s, new %s[] { [[%s]] }, %s)", Internal.class, out.ref(getStrategy().getFullJavaIdentifier(foreignKey.getKeyTable()), 2), DSL.class, @@ -1135,6 +1216,31 @@ public class JavaGenerator extends AbstractGenerator { ); } + + + + + + + + + + + + + + + + + + + + + + + + + protected void generateRecords(SchemaDefinition schema) { log.info("Generating table records"); @@ -1226,35 +1332,44 @@ public class JavaGenerator extends AbstractGenerator { if (generateInterfaces()) interfaces.add(out.ref(getStrategy().getFullJavaClassName(tableUdtOrEmbeddable, Mode.INTERFACE))); - if (scala) { - if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) { - out.println("private object %s {", className); - out.println("val FIELDS: Array[%s [_] ] = Array(", Field.class); - - String separator = " "; - for (EmbeddableColumnDefinition column : ((EmbeddableDefinition) tableUdtOrEmbeddable).getColumns()) { - final String colIdentifier = out.ref(getStrategy().getFullJavaIdentifier(column.getColumn()), colRefSegments(column)); - - out.println("%s%s.field(%s.name(\"%s\"), %s.getDataType)", separator, DSL.class, DSL.class, column.getOutputName(), colIdentifier); - separator = ", "; - } - - out.println(")"); - out.println("}"); - out.println(); - } - } - if (scala) if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) - out.println("class %s extends %s[%s](%s.FIELDS:_*)[[before= with ][separator= with ][%s]] {", className, baseClass, className, className, interfaces); + out.println("class %s extends %s[%s](%s.fields(%s.%s):_*)[[before= with ][separator= with ][%s]] {", + className, + baseClass, + className, + Internal.class, + out.ref(getStrategy().getFullJavaIdentifier(((EmbeddableDefinition) tableUdtOrEmbeddable).getTable()), 2), + getStrategy().getJavaIdentifier(tableUdtOrEmbeddable), + interfaces + ); else - out.println("class %s extends %s[%s](%s)[[before= with ][separator= with ][%s]] {", className, baseClass, className, tableIdentifier, interfaces); + out.println("class %s extends %s[%s](%s)[[before= with ][separator= with ][%s]] {", + className, + baseClass, + className, + tableIdentifier, + interfaces + ); else if (kotlin) if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) - out.println("class %s() : %s<%s>(*FIELDS)[[before=, ][%s]] {", className, baseClass, className, interfaces); + out.println("class %s() : %s<%s>(*%s.fields(%s.%s))[[before=, ][%s]] {", + className, + baseClass, + className, + Internal.class, + out.ref(getStrategy().getFullJavaIdentifier(((EmbeddableDefinition) tableUdtOrEmbeddable).getTable()), 2), + getStrategy().getJavaIdentifier(tableUdtOrEmbeddable), + interfaces + ); else - out.println("class %s() : %s<%s>(%s)[[before=, ][%s]] {", className, baseClass, className, tableIdentifier, interfaces); + out.println("class %s() : %s<%s>(%s)[[before=, ][%s]] {", + className, + baseClass, + className, + tableIdentifier, + interfaces + ); else out.println("public class %s extends %s<%s>[[before= implements ][%s]] {", className, baseClass, className, interfaces); @@ -1293,7 +1408,7 @@ public class JavaGenerator extends AbstractGenerator { } if (tableUdtOrEmbeddable instanceof TableDefinition) { - List embeddables = ((TableDefinition) tableUdtOrEmbeddable).getEmbeddables(); + List embeddables = ((TableDefinition) tableUdtOrEmbeddable).getReferencedEmbeddables(); for (int i = 0; i < embeddables.size(); i++) { EmbeddableDefinition embeddable = embeddables.get(i); @@ -1407,7 +1522,16 @@ public class JavaGenerator extends AbstractGenerator { printDeprecationIfUnknownType(out, colTypeFull); if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) - out.println("override def field%s: %s[%s] = %s.FIELDS(%s).asInstanceOf[%s [%s] ]", i, Field.class, colType, i - 1, className, Field.class, colType); + out.println("override def field%s: %s[%s] = %s.fields(%s.%s):_*.asInstanceOf[%s [%s] ]", + i, + Field.class, + colType, + Internal.class, + out.ref(getStrategy().getFullJavaIdentifier(((EmbeddableDefinition) tableUdtOrEmbeddable).getTable()), 2), + getStrategy().getJavaIdentifier(tableUdtOrEmbeddable), + Field.class, + colType + ); else out.println("override def field%s: %s[%s] = %s", i, Field.class, colType, colIdentifier); } @@ -1415,7 +1539,16 @@ public class JavaGenerator extends AbstractGenerator { printDeprecationIfUnknownType(out, colTypeFull); if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) - out.println("override fun field%s(): %s<%s?> = FIELDS[%s] as %s<%s>", i, Field.class, colType, i - 1, Field.class, colType); + out.println("override fun field%s(): %s<%s?> = %s.fields(%s.%s) as %s<%s>", + i, + Field.class, + colType, + Internal.class, + out.ref(getStrategy().getFullJavaIdentifier(((EmbeddableDefinition) tableUdtOrEmbeddable).getTable()), 2), + getStrategy().getJavaIdentifier(tableUdtOrEmbeddable), + Field.class, + colType + ); else out.println("override fun field%s(): %s<%s?> = %s", i, Field.class, colType, colIdentifier); } @@ -1597,47 +1730,19 @@ public class JavaGenerator extends AbstractGenerator { printFromAndInto(out, tableUdtOrEmbeddable, Mode.RECORD); + // [#2530] TODO Implement this for Scala & Kotlin if (scala) {} - else if (kotlin) { - if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) { - out.println(); - out.println("private companion object {"); - out.println("val FIELDS: Array<%s<*>> = arrayOf(", Field.class); - - String separator = " "; - for (EmbeddableColumnDefinition column : ((EmbeddableDefinition) tableUdtOrEmbeddable).getColumns()) { - final String colIdentifier = out.ref(getStrategy().getFullJavaIdentifier(column.getColumn()), colRefSegments(column)); - - out.println("%s%s.field(%s.name(\"%s\"), %s.dataType)", separator, DSL.class, DSL.class, column.getOutputName(), colIdentifier); - separator = ", "; - } - - out.println(")"); - out.println("}"); - } - } + else if (kotlin) {} else { out.header("Constructors"); - - if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) { - out.println(); - out.println("private static final %s[] FIELDS = {", Field.class); - - for (EmbeddableColumnDefinition column : ((EmbeddableDefinition) tableUdtOrEmbeddable).getColumns()) { - final String colIdentifier = out.ref(getStrategy().getFullJavaIdentifier(column.getColumn()), colRefSegments(column)); - - out.println("%s.field(%s.name(\"%s\"), %s.getDataType()),", DSL.class, DSL.class, column.getOutputName(), colIdentifier); - } - - out.println("};"); - out.println(); - } - out.javadoc("Create a detached %s", className); out.println("public %s() {", className); if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) - out.println("super(FIELDS);"); + out.println("super(%s.fields(%s.%s));", + Internal.class, + out.ref(getStrategy().getFullJavaIdentifier(((EmbeddableDefinition) tableUdtOrEmbeddable).getTable()), 2), + getStrategy().getJavaIdentifier(tableUdtOrEmbeddable)); else out.println("super(%s);", tableIdentifier); out.println("}"); @@ -4299,29 +4404,37 @@ public class JavaGenerator extends AbstractGenerator { final String columnName = column.getName(); final List converter = out.ref(list(column.getType(resolver()).getConverter())); final List binding = out.ref(list(column.getType(resolver()).getBinding())); + final String columnVisibility = + + + + + scala || kotlin ? + "" : + "public "; if (!printDeprecationIfUnknownType(out, columnTypeFull)) out.javadoc("The column %s.[[before= ][%s]]", column.getQualifiedOutputName(), list(escapeEntities(comment(column)))); if (scala) { - out.println("val %s: %s[%s, %s] = createField(%s.name(\"%s\"), %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")", - columnId, TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding); + out.println("%sval %s: %s[%s, %s] = createField(%s.name(\"%s\"), %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")", + columnVisibility, columnId, TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding); } else if (kotlin) { - out.println("val %s: %s<%s, %s?> = createField(%s.name(\"%s\"), %s, this, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")", - columnId, TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding); + out.println("%sval %s: %s<%s, %s?> = createField(%s.name(\"%s\"), %s, this, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")", + columnVisibility, columnId, TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding); } else { String isStatic = generateInstanceFields() ? "" : "static "; String tableRef = generateInstanceFields() ? "this" : out.ref(getStrategy().getJavaIdentifier(table), 2); - out.println("public %sfinal %s<%s, %s> %s = createField(%s.name(\"%s\"), %s, %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ");", - isStatic, TableField.class, recordType, columnType, columnId, DSL.class, columnName, columnTypeRef, tableRef, escapeString(comment(column)), converter, binding); + out.println("%s%sfinal %s<%s, %s> %s = createField(%s.name(\"%s\"), %s, %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ");", + columnVisibility, isStatic, TableField.class, recordType, columnType, columnId, DSL.class, columnName, columnTypeRef, tableRef, escapeString(comment(column)), converter, binding); } } // [#2530] Embeddable types - for (EmbeddableDefinition embeddable : table.getEmbeddables()) { + for (EmbeddableDefinition embeddable : table.getReferencedEmbeddables()) { final String columnId = out.ref(getStrategy().getJavaIdentifier(embeddable), colRefSegments(null)); final String columnType = out.ref(getStrategy().getFullJavaClassName(embeddable, Mode.RECORD)); @@ -4332,14 +4445,14 @@ public class JavaGenerator extends AbstractGenerator { out.javadoc("The embeddable type %s.", embeddable.getOutputName()); if (scala) - out.println("val %s: %s[%s, %s] = %s.createEmbeddable(%s.name(\"%s\"), classOf[%s], this, [[%s]])", - columnId, TableField.class, recordType, columnType, Internal.class, DSL.class, embeddable.getName(), columnType, columnIds); + out.println("val %s: %s[%s, %s] = %s.createEmbeddable(%s.name(\"%s\"), classOf[%s], %s, this, [[%s]])", + columnId, TableField.class, recordType, columnType, Internal.class, DSL.class, escapeString(embeddable.getName()), columnType, embeddable.replacesFields(), columnIds); else if (kotlin) - out.println("val %s: %s<%s, %s> = %s.createEmbeddable(%s.name(\"%s\"), %s::class.java, this, [[%s]])", - columnId, TableField.class, recordType, columnType, Internal.class, DSL.class, embeddable.getName(), columnType, columnIds); + out.println("val %s: %s<%s, %s> = %s.createEmbeddable(%s.name(\"%s\"), %s::class.java, %s, this, [[%s]])", + columnId, TableField.class, recordType, columnType, Internal.class, DSL.class, escapeString(embeddable.getName()), columnType, embeddable.replacesFields(), columnIds); else - out.println("public final %s<%s, %s> %s = %s.createEmbeddable(%s.name(\"%s\"), %s.class, this, [[%s]]);", - TableField.class, recordType, columnType, columnId, Internal.class, DSL.class, embeddable.getName(), columnType, columnIds); + out.println("public final %s<%s, %s> %s = %s.createEmbeddable(%s.name(\"%s\"), %s.class, %s, this, [[%s]]);", + TableField.class, recordType, columnType, columnId, Internal.class, DSL.class, escapeString(embeddable.getName()), columnType, embeddable.replacesFields(), columnIds); } out.println(); @@ -5073,7 +5186,10 @@ public class JavaGenerator extends AbstractGenerator { for (EmbeddableDefinition embeddable : database.getEmbeddables(schema)) { try { - generateEmbeddable(schema, embeddable); + + // [#6124] [#10481] Don't generate embeddable types for FKs + if (embeddable.getTable().equals(embeddable.getReferencingTable())) + generateEmbeddable(schema, embeddable); } catch (Exception e) { log.error("Error while generating embeddable " + embeddable, e); 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 3480dd8727..6024486e10 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java @@ -38,12 +38,14 @@ package org.jooq.meta; +import static java.lang.Boolean.TRUE; import static org.jooq.Log.Level.ERROR; import static org.jooq.SQLDialect.CUBRID; import static org.jooq.SQLDialect.FIREBIRD; import static org.jooq.SQLDialect.SQLITE; import static org.jooq.impl.DSL.falseCondition; import static org.jooq.meta.AbstractTypedElementDefinition.customType; +import static org.jooq.tools.StringUtils.defaultIfBlank; import static org.jooq.tools.StringUtils.defaultIfEmpty; import java.io.File; @@ -155,6 +157,8 @@ public abstract class AbstractDatabase implements Database { private String[] syntheticPrimaryKeys; private String[] overridePrimaryKeys; private String[] syntheticIdentities; + private boolean embeddablePrimaryKeys = false; + private boolean embeddableUniqueKeys = false; private boolean supportsUnsignedTypes; private boolean integerDisplayWidths; private boolean ignoreProcedureReturnValues; @@ -208,8 +212,9 @@ public abstract class AbstractDatabase implements Database { private transient Map> foreignKeysBySchema; private transient Map> checkConstraintsBySchema; private transient Map> tablesBySchema; - private transient Map> embeddablesBySchema; - private transient Map> embeddablesByTable; + private transient Map> embeddablesByDefiningSchema; + private transient Map> embeddablesByDefiningTable; + private transient Map> embeddablesByReferencingTable; private transient Map> enumsBySchema; private transient Map> domainsBySchema; private transient Map> udtsBySchema; @@ -1533,10 +1538,8 @@ public abstract class AbstractDatabase implements Database { return filterSchema(identities, schema, identitiesBySchema); } - - @Override - public final List getUniqueKeys(SchemaDefinition schema) { + public final List getUniqueKeys() { if (uniqueKeys == null) { uniqueKeys = new ArrayList<>(); @@ -1549,14 +1552,19 @@ public abstract class AbstractDatabase implements Database { sort(uniqueKeys); } - if (uniqueKeysBySchema == null) - uniqueKeysBySchema = new LinkedHashMap<>(); - - return filterSchema(uniqueKeys, schema, uniqueKeysBySchema); + return uniqueKeys; } @Override - public final List getForeignKeys(SchemaDefinition schema) { + public final List getUniqueKeys(SchemaDefinition schema) { + if (uniqueKeysBySchema == null) + uniqueKeysBySchema = new LinkedHashMap<>(); + + return filterSchema(getUniqueKeys(), schema, uniqueKeysBySchema); + } + + @Override + public final List getForeignKeys() { if (foreignKeys == null) { foreignKeys = new ArrayList<>(); @@ -1569,10 +1577,15 @@ public abstract class AbstractDatabase implements Database { sort(foreignKeys); } + return foreignKeys; + } + + @Override + public final List getForeignKeys(SchemaDefinition schema) { if (foreignKeysBySchema == null) foreignKeysBySchema = new LinkedHashMap<>(); - return filterSchema(foreignKeys, schema, foreignKeysBySchema); + return filterSchema(getForeignKeys(), schema, foreignKeysBySchema); } @Override @@ -1596,7 +1609,7 @@ public abstract class AbstractDatabase implements Database { } @Override - public final List getTables(SchemaDefinition schema) { + public final List getTables() { if (tables == null) { tables = new ArrayList<>(); @@ -1615,10 +1628,15 @@ public abstract class AbstractDatabase implements Database { log.info("Tables excluded"); } + return tables; + } + + @Override + public final List getTables(SchemaDefinition schema) { if (tablesBySchema == null) tablesBySchema = new LinkedHashMap<>(); - return filterSchema(tables, schema, tablesBySchema); + return filterSchema(getTables(), schema, tablesBySchema); } @Override @@ -1776,37 +1794,41 @@ public abstract class AbstractDatabase implements Database { } @Override - public final List getEmbeddables() { - List result = new ArrayList<>(); + public boolean embeddablePrimaryKeys() { + return embeddablePrimaryKeys; + } - for (SchemaDefinition schema : getSchemata()) { - for (TableDefinition table : getTables(schema)) { - for (Embeddable embeddable : getConfiguredEmbeddables()) { - List columns = new ArrayList<>(); - List names = new ArrayList<>(); + @SuppressWarnings("unused") + @Override + public void setEmbeddablePrimaryKeys(boolean embeddablePrimaryKeys) { - for (EmbeddableField embeddableField : embeddable.getFields()) { - boolean matched = false; - for (ColumnDefinition column : table.getColumns()) - if (matches(patterns.pattern(embeddableField.getExpression()), column)) - if (matched) - log.warn("EmbeddableField configuration matched several columns in table " + table + ": " + embeddableField); - else - matched = columns.add(column) && names.add(defaultIfEmpty(embeddableField.getName(), column.getName())); - } - if (columns.size() == embeddable.getFields().size()) - result.add(new DefaultEmbeddableDefinition(embeddable.getName(), names, table, columns)); - } - } - } + if (embeddablePrimaryKeys) + log.info("Commercial feature", "Embeddable primary and unique keys are a commercial only feature. Please consider upgrading to the jOOQ Professional Edition"); - return result; + this.embeddablePrimaryKeys = embeddablePrimaryKeys; } @Override - public final List getEmbeddables(SchemaDefinition schema) { + public boolean embeddableUniqueKeys() { + return embeddableUniqueKeys; + } + + @SuppressWarnings("unused") + @Override + public void setEmbeddableUniqueKeys(boolean embeddableUniqueKeys) { + + + + if (embeddableUniqueKeys) + log.info("Commercial feature", "Embeddable primary and unique keys are a commercial only feature. Please consider upgrading to the jOOQ Professional Edition"); + + this.embeddableUniqueKeys = embeddableUniqueKeys; + } + + @Override + public final List getEmbeddables() { if (embeddables == null) { embeddables = new ArrayList<>(); @@ -1814,7 +1836,7 @@ public abstract class AbstractDatabase implements Database { onError(ERROR, "Error while fetching embeddables", new ExceptionRunnable() { @Override public void run() throws Exception { - List r = getEmbeddables(); + List r = getEmbeddables0(); embeddables = sort(r); // indexes = sort(filterExcludeInclude(r)); TODO Support include / exclude for indexes (and constraints!) @@ -1826,18 +1848,145 @@ public abstract class AbstractDatabase implements Database { log.info("Embeddables excluded"); } - if (embeddablesBySchema == null) - embeddablesBySchema = new LinkedHashMap<>(); + return embeddables; + } - return filterSchema(embeddables, schema, embeddablesBySchema); + @Override + public final List getEmbeddables(SchemaDefinition schema) { + if (embeddablesByDefiningSchema == null) + embeddablesByDefiningSchema = new LinkedHashMap<>(); + + return filterSchema(getEmbeddables(), schema, embeddablesByDefiningSchema); } @Override public final List getEmbeddables(TableDefinition table) { - if (embeddablesByTable == null) - embeddablesByTable = new LinkedHashMap<>(); + if (embeddablesByDefiningTable == null) + embeddablesByDefiningTable = new LinkedHashMap<>(); - return filterTable(getEmbeddables(table.getSchema()), table, embeddablesByTable); + return filterTable(getEmbeddables(table.getSchema()), table, embeddablesByDefiningTable); + } + + @Override + public final List getEmbeddablesByReferencingTable(TableDefinition table) { + if (embeddablesByReferencingTable == null) + embeddablesByReferencingTable = new LinkedHashMap<>(); + + return filterReferencingTable(getEmbeddables(), table, embeddablesByReferencingTable); + } + + private final List getEmbeddables0() { + Map result = new LinkedHashMap<>(); + + for (TableDefinition table : getTables()) { + for (Embeddable embeddable : getConfiguredEmbeddables()) { + List columns = new ArrayList<>(); + List names = new ArrayList<>(); + + for (EmbeddableField embeddableField : embeddable.getFields()) { + boolean matched = false; + + for (ColumnDefinition column : table.getColumns()) + if (matches(patterns.pattern(embeddableField.getExpression()), column)) + if (matched) + log.warn("EmbeddableField configuration matched several columns in table " + table + ": " + embeddableField); + else + matched = columns.add(column) && names.add(defaultIfEmpty(embeddableField.getName(), column.getName())); + } + + + if (columns.size() == embeddable.getFields().size()) { + Name name = table.getQualifiedNamePart().append(embeddable.getName()); + + if (result.containsKey(name)) + log.warn("Embeddable configuration", "Table " + table + " already has embeddable " + embeddable); + else + result.put( + name, + new DefaultEmbeddableDefinition( + embeddable.getName(), + table, + names, + defaultIfBlank(embeddable.getReferencingName(), embeddable.getName()), + table, + columns, + TRUE.equals(embeddable.isReplacesFields()) + ) + ); + } + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + return new ArrayList<>(result.values()); } @Override @@ -2219,6 +2368,30 @@ public abstract class AbstractDatabase implements Database { return result; } + private final List filterReferencingTable(List definitions, TableDefinition table, Map> cache) { + List result = cache.get(table); + + if (result == null) { + result = filterReferencingTable(definitions, table); + cache.put(table, result); + } + + return result; + } + + private final List filterReferencingTable(List definitions, TableDefinition table) { + if (table == null) + return definitions; + + List result = new ArrayList<>(); + + for (T definition : definitions) + if (definition.getReferencingTable().equals(table)) + result.add(definition); + + return result; + } + @Override public final List filterExcludeInclude(List definitions) { List result = filterExcludeInclude(definitions, excludes, includes, filters); @@ -2319,7 +2492,7 @@ public abstract class AbstractDatabase implements Database { onError(ERROR, "Error while fetching unique keys", new ExceptionRunnable() { @Override public void run() throws Exception { - loadUniqueKeys(result); + loadUniqueKeys(result); } }); } @@ -2328,7 +2501,7 @@ public abstract class AbstractDatabase implements Database { onError(ERROR, "Error while fetching foreign keys", new ExceptionRunnable() { @Override public void run() throws Exception { - loadForeignKeys(result); + loadForeignKeys(result); } }); } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java index 64a232d4d6..7836e5662a 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java @@ -80,6 +80,11 @@ implements TableDefinition { return getDatabase().getEmbeddables(this); } + @Override + public final List getReferencedEmbeddables() { + return getDatabase().getEmbeddablesByReferencingTable(this); + } + @Override public final UniqueKeyDefinition getPrimaryKey() { for (ColumnDefinition column : getColumns()) diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java index d1c6c0f541..4d9eaaa2bb 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java @@ -40,6 +40,8 @@ package org.jooq.meta; import java.util.List; +// ... + /** * An interface defining a column of a table. * @@ -76,4 +78,22 @@ public interface ColumnDefinition extends TypedElementDefinition getIndexes(TableDefinition schema); + /** + * The unique keys contained in this database. + */ + List getUniqueKeys(); + /** * The unique keys contained in this database. */ List getUniqueKeys(SchemaDefinition schema); + /** + * The foreign keys contained in this database. + */ + List getForeignKeys(); + /** * The foreign keys contained in this database. */ @@ -129,6 +139,11 @@ public interface Database extends AutoCloseable { */ List getCheckConstraints(SchemaDefinition schema); + /** + * The tables contained in this database. + */ + List getTables(); + /** * The tables contained in this database. */ @@ -160,15 +175,20 @@ public interface Database extends AutoCloseable { List getEmbeddables(); /** - * Get all embeddables for a given schema. + * Get all embeddables for a given defining schema. */ List getEmbeddables(SchemaDefinition schema); /** - * Get all embeddables for a given table. + * Get all embeddables for a given defining table. */ List getEmbeddables(TableDefinition table); + /** + * Get all embeddables for a given referencing table. + */ + List getEmbeddablesByReferencingTable(TableDefinition table); + /** * The enum UDTs defined in this database. */ @@ -868,6 +888,26 @@ public interface Database extends AutoCloseable { */ List getConfiguredEmbeddables(); + /** + * Whether embeddable types for primary keys should be generated. + */ + boolean embeddablePrimaryKeys(); + + /** + * Whether embeddable types for primary keys should be generated. + */ + void setEmbeddablePrimaryKeys(boolean embeddablePrimaryKeys); + + /** + * Whether embeddable types for unique keys should be generated. + */ + boolean embeddableUniqueKeys(); + + /** + * Whether embeddable types for unique keys should be generated. + */ + void setEmbeddableUniqueKeys(boolean embeddableUniqueKeys); + /** * Get the dialect for this database. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultColumnDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultColumnDefinition.java index b4fd2aa0e7..b0103b2b38 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultColumnDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultColumnDefinition.java @@ -40,6 +40,8 @@ package org.jooq.meta; import static java.util.Collections.singletonList; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.jooq.tools.JooqLogger; @@ -53,9 +55,11 @@ public class DefaultColumnDefinition extends AbstractTypedElementDefinition implements ColumnDefinition { - private static final JooqLogger log = JooqLogger.getLogger(DefaultColumnDefinition.class); - private final int position; - private final boolean isIdentity; + private static final JooqLogger log = JooqLogger.getLogger(DefaultColumnDefinition.class); + private final int position; + private final boolean isIdentity; + private transient List containedInEmbeddables; + private transient EmbeddableDefinition replacedByEmbeddable; public DefaultColumnDefinition(TableDefinition table, String name, int position, DataTypeDefinition type, boolean isIdentity, String comment) { @@ -110,4 +114,35 @@ public class DefaultColumnDefinition public final boolean isNullable() { return getType().isNullable(); } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java index 5cf98235b4..744070d404 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java @@ -41,6 +41,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import org.jooq.tools.JooqLogger; + /** * @author Lukas Eder */ @@ -48,28 +50,74 @@ public class DefaultEmbeddableDefinition extends AbstractElementContainerDefinition implements EmbeddableDefinition { - private final List columnNames; - private final TableDefinition table; + private static final JooqLogger log = JooqLogger.getLogger(DefaultEmbeddableDefinition.class); + private final TableDefinition definingTable; + private final List definingColumnNames; + private final String referencingName; + private final TableDefinition referencingTable; private final List embeddableColumns; + private final boolean replacesFields; - public DefaultEmbeddableDefinition(String name, List columnNames, TableDefinition table, List columns) { - super(table.getSchema(), name, ""); + @SuppressWarnings("unused") + public DefaultEmbeddableDefinition( + String definingName, + TableDefinition definingTable, + List definingColumnNames, + String referencingName, + TableDefinition referencingTable, + List referencingColumns, + boolean replacesFields + ) { + super(definingTable.getSchema(), definingName, ""); - this.columnNames = columnNames; - this.table = table; + this.definingColumnNames = definingColumnNames; + this.definingTable = definingTable; + this.referencingName = referencingName; + this.referencingTable = referencingTable; this.embeddableColumns = new ArrayList<>(); + this.replacesFields = replacesFields; - for (int i = 0; i < columns.size(); i++) - embeddableColumns.add(new DefaultEmbeddableColumnDefinition(this, columnNames.get(i), columns.get(i), i)); + + + + log.info("Commercial feature", "Embeddables replacing fields is a commercial only feature. Please upgrade to the jOOQ Professional Edition"); + + for (int i = 0; i < referencingColumns.size(); i++) + embeddableColumns.add(new DefaultEmbeddableColumnDefinition(this, definingColumnNames.get(i), referencingColumns.get(i), i)); } @Override public final TableDefinition getTable() { - return table; + return getDefiningTable(); } @Override - protected List getElements0() throws SQLException { + public final TableDefinition getDefiningTable() { + return definingTable; + } + + @Override + public final String getReferencingName() { + return getReferencingInputName(); + } + + @Override + public final String getReferencingInputName() { + return referencingName; + } + + @Override + public final String getReferencingOutputName() { + return referencingName; + } + + @Override + public final TableDefinition getReferencingTable() { + return referencingTable; + } + + @Override + protected final List getElements0() throws SQLException { return embeddableColumns; } @@ -92,4 +140,9 @@ public class DefaultEmbeddableDefinition public final EmbeddableColumnDefinition getColumn(int columnIndex) { return getElement(columnIndex); } + + @Override + public final boolean replacesFields() { + return replacesFields; + } } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java index 1b25359ea9..959246adeb 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java @@ -44,9 +44,9 @@ import java.util.Set; public class DefaultForeignKeyDefinition extends AbstractConstraintDefinition implements ForeignKeyDefinition { - private final List fkColumns; - private final List ukColumns; - private final UniqueKeyDefinition uk; + private final List fkColumns; + private final List ukColumns; + private final UniqueKeyDefinition uk; public DefaultForeignKeyDefinition(SchemaDefinition schema, String name, TableDefinition table, UniqueKeyDefinition uniqueKey) { this(schema, name, table, uniqueKey, true); @@ -75,6 +75,11 @@ public class DefaultForeignKeyDefinition extends AbstractConstraintDefinition im return uk; } + @Override + public UniqueKeyDefinition resolveReferencedKey() { + return uk.resolveReferencedKey(); + } + @Override public TableDefinition getReferencedTable() { return uk.getTable(); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultUniqueKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultUniqueKeyDefinition.java index 6f6379cfaf..8582adbfa9 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultUniqueKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultUniqueKeyDefinition.java @@ -40,11 +40,16 @@ package org.jooq.meta; import java.util.ArrayList; import java.util.List; +import org.jooq.tools.JooqLogger; + public class DefaultUniqueKeyDefinition extends AbstractConstraintDefinition implements UniqueKeyDefinition { + private static final JooqLogger log = JooqLogger.getLogger(DefaultUniqueKeyDefinition.class); private final List foreignKeys; private final List keyColumns; private final boolean isPrimaryKey; + private transient boolean resolvedUKCalculated; + private transient UniqueKeyDefinition resolvedUK; public DefaultUniqueKeyDefinition(SchemaDefinition schema, String name, TableDefinition table, boolean isPrimaryKey) { this(schema, name, table, isPrimaryKey, true); @@ -72,4 +77,28 @@ public class DefaultUniqueKeyDefinition extends AbstractConstraintDefinition imp public List getForeignKeys() { return foreignKeys; } + + @Override + public final UniqueKeyDefinition resolveReferencedKey() { + if (!resolvedUKCalculated) { + resolvedUKCalculated = true; + + ForeignKeyDefinition candidate = null; + for (ForeignKeyDefinition fk : getTable().getForeignKeys()) { + if (keyColumns.equals(fk.getKeyColumns())) { + if (candidate == null) { + candidate = fk; + } + else { + log.info("Cannot resolve key", (isPrimaryKey ? "Primary" : "Unique") + " key coincides with at least two foreign keys: " + candidate + " and " + fk); + return null; + } + } + } + + resolvedUK = candidate == null ? this : candidate.resolveReferencedKey(); + } + + return resolvedUK; + } } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java index 3896fe7ec1..ba5d64c7a2 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java @@ -48,23 +48,56 @@ import java.util.List; public interface EmbeddableDefinition extends TableElementDefinition { /** - * All columns in the type, table or view. + * The table defining the embeddable (same as {@link #getTable()}). + */ + TableDefinition getDefiningTable(); + + /** + * The referencing name of this embeddable, if it differs from the defining + * name ({@link #getName()}). + */ + String getReferencingName(); + + /** + * The referencing input name of this embeddable, if it differs from the defining + * name ({@link #getInputName()}). + */ + String getReferencingInputName(); + + /** + * The referencing output name of this embeddable, if it differs from the defining + * name ({@link #getOutputName()}). + */ + String getReferencingOutputName(); + + /** + * The table referencing the embeddable. + */ + TableDefinition getReferencingTable(); + + /** + * All referencing columns in the type, table or view. */ List getColumns(); /** - * Get a column in this type by its name. + * Get a referencing column in this type by its name. */ EmbeddableColumnDefinition getColumn(String columnName); /** - * Get a column in this type by its name. + * Get a referencing column in this type by its name. */ EmbeddableColumnDefinition getColumn(String columnName, boolean ignoreCase); /** - * Get a column in this type by its index (starting at 0). + * Get a referencing column in this type by its index (starting at 0). */ EmbeddableColumnDefinition getColumn(int columnIndex); + /** + * Whether this embeddable replaces the fields it represents. + */ + boolean replacesFields(); + } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java index 069e5b62aa..d33b9322d3 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java @@ -64,6 +64,16 @@ public interface ForeignKeyDefinition extends ConstraintDefinition { */ UniqueKeyDefinition getReferencedKey(); + /** + * Resolve a referenced key. + *

+ * If {@link #getReferencedKey()} coincides itself with a foreign key, + * resolve that foreign key recursively. In case of ambiguity (two foreign + * keys coinciding with a single unique key), this returns + * null. + */ + UniqueKeyDefinition resolveReferencedKey(); + /** * The definition of the referenced table. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java index 399d09c600..891791bee5 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java @@ -71,10 +71,15 @@ public interface TableDefinition extends Definition { ColumnDefinition getColumn(int columnIndex); /** - * All embeddable types in the table. + * All embeddable types in this defining table. */ List getEmbeddables(); + /** + * All embeddable types in this referencing table. + */ + List getReferencedEmbeddables(); + /** * Get the indexes for this table. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/UniqueKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/UniqueKeyDefinition.java index b9ecc8d379..3709f2710d 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/UniqueKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/UniqueKeyDefinition.java @@ -63,4 +63,14 @@ public interface UniqueKeyDefinition extends ConstraintDefinition { * The foreign keys referencing this primary key */ List getForeignKeys(); + + /** + * Resolve a referenced key. + *

+ * If this key coincides with a foreign key, resolve that foreign key + * recursively. In case of ambiguity (two foreign keys coinciding with a + * single unique key), this returns null. + */ + UniqueKeyDefinition resolveReferencedKey(); + } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java index 86b55e67db..777d036c35 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java @@ -133,6 +133,10 @@ public class Database implements Serializable, XMLAppendable @XmlElement(defaultValue = "") @XmlJavaTypeAdapter(StringAdapter.class) protected String orderProvider = ""; + @XmlElement(defaultValue = "false") + protected Boolean embeddablePrimaryKeys = false; + @XmlElement(defaultValue = "false") + protected Boolean embeddableUniqueKeys = false; @XmlElement(defaultValue = "true") protected Boolean forceIntegerTypesOnZeroScaleDecimals = true; protected Boolean tableValuedFunctions; @@ -1355,6 +1359,54 @@ public class Database implements Serializable, XMLAppendable this.orderProvider = value; } + /** + * Whether wrapper types should be generated for primary key columns, and for their referencing foreign keys. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isEmbeddablePrimaryKeys() { + return embeddablePrimaryKeys; + } + + /** + * Sets the value of the embeddablePrimaryKeys property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setEmbeddablePrimaryKeys(Boolean value) { + this.embeddablePrimaryKeys = value; + } + + /** + * Whether wrapper types should be generated for unique key columns, and for their referencing foreign keys. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isEmbeddableUniqueKeys() { + return embeddableUniqueKeys; + } + + /** + * Sets the value of the embeddableUniqueKeys property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setEmbeddableUniqueKeys(Boolean value) { + this.embeddableUniqueKeys = value; + } + /** * Historically, zero-scale decimal types are generated as their most appropriate, corresponding integer type (e.g. NUMBER(2, 0) and less: Byte). This allows for turning off this feature. In case of conflict between this rule and actual {@link #getForcedTypes()}, the latter will win. * @@ -1989,6 +2041,16 @@ public class Database implements Serializable, XMLAppendable return this; } + public Database withEmbeddablePrimaryKeys(Boolean value) { + setEmbeddablePrimaryKeys(value); + return this; + } + + public Database withEmbeddableUniqueKeys(Boolean value) { + setEmbeddableUniqueKeys(value); + return this; + } + public Database withForceIntegerTypesOnZeroScaleDecimals(Boolean value) { setForceIntegerTypesOnZeroScaleDecimals(value); return this; @@ -2215,6 +2277,8 @@ public class Database implements Serializable, XMLAppendable builder.append("schemaVersionProvider", schemaVersionProvider); builder.append("catalogVersionProvider", catalogVersionProvider); builder.append("orderProvider", orderProvider); + builder.append("embeddablePrimaryKeys", embeddablePrimaryKeys); + builder.append("embeddableUniqueKeys", embeddableUniqueKeys); builder.append("forceIntegerTypesOnZeroScaleDecimals", forceIntegerTypesOnZeroScaleDecimals); builder.append("tableValuedFunctions", tableValuedFunctions); builder.append("logSlowQueriesAfterSeconds", logSlowQueriesAfterSeconds); @@ -2634,6 +2698,24 @@ public class Database implements Serializable, XMLAppendable return false; } } + if (embeddablePrimaryKeys == null) { + if (other.embeddablePrimaryKeys!= null) { + return false; + } + } else { + if (!embeddablePrimaryKeys.equals(other.embeddablePrimaryKeys)) { + return false; + } + } + if (embeddableUniqueKeys == null) { + if (other.embeddableUniqueKeys!= null) { + return false; + } + } else { + if (!embeddableUniqueKeys.equals(other.embeddableUniqueKeys)) { + return false; + } + } if (forceIntegerTypesOnZeroScaleDecimals == null) { if (other.forceIntegerTypesOnZeroScaleDecimals!= null) { return false; @@ -2783,6 +2865,8 @@ public class Database implements Serializable, XMLAppendable result = ((prime*result)+((schemaVersionProvider == null)? 0 :schemaVersionProvider.hashCode())); result = ((prime*result)+((catalogVersionProvider == null)? 0 :catalogVersionProvider.hashCode())); result = ((prime*result)+((orderProvider == null)? 0 :orderProvider.hashCode())); + result = ((prime*result)+((embeddablePrimaryKeys == null)? 0 :embeddablePrimaryKeys.hashCode())); + result = ((prime*result)+((embeddableUniqueKeys == null)? 0 :embeddableUniqueKeys.hashCode())); result = ((prime*result)+((forceIntegerTypesOnZeroScaleDecimals == null)? 0 :forceIntegerTypesOnZeroScaleDecimals.hashCode())); result = ((prime*result)+((tableValuedFunctions == null)? 0 :tableValuedFunctions.hashCode())); result = ((prime*result)+((logSlowQueriesAfterSeconds == null)? 0 :logSlowQueriesAfterSeconds.hashCode())); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Embeddable.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Embeddable.java index 71609b7567..359e08e984 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Embeddable.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Embeddable.java @@ -35,12 +35,16 @@ public class Embeddable implements Serializable, XMLAppendable private final static long serialVersionUID = 31400L; @XmlJavaTypeAdapter(StringAdapter.class) protected String name; + @XmlJavaTypeAdapter(StringAdapter.class) + protected String referencingName; + @XmlElement(defaultValue = "false") + protected Boolean replacesFields = false; @XmlElementWrapper(name = "fields") @XmlElement(name = "field") protected List fields; /** - * The name of the embeddable type + * The defining name of the embeddable type. * */ public String getName() { @@ -48,13 +52,53 @@ public class Embeddable implements Serializable, XMLAppendable } /** - * The name of the embeddable type + * The defining name of the embeddable type. * */ public void setName(String value) { this.name = value; } + /** + * The referencing name of the embeddable type, defaulting to the defining name. + * + */ + public String getReferencingName() { + return referencingName; + } + + /** + * The referencing name of the embeddable type, defaulting to the defining name. + * + */ + public void setReferencingName(String value) { + this.referencingName = value; + } + + /** + * Specify that the embeddable field replaces its underlying fields in code generation output, and when working with asterisks. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isReplacesFields() { + return replacesFields; + } + + /** + * Sets the value of the replacesFields property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setReplacesFields(Boolean value) { + this.replacesFields = value; + } + public List getFields() { if (fields == null) { fields = new ArrayList(); @@ -67,7 +111,7 @@ public class Embeddable implements Serializable, XMLAppendable } /** - * The name of the embeddable type + * The defining name of the embeddable type. * */ public Embeddable withName(String value) { @@ -75,6 +119,20 @@ public class Embeddable implements Serializable, XMLAppendable return this; } + /** + * The referencing name of the embeddable type, defaulting to the defining name. + * + */ + public Embeddable withReferencingName(String value) { + setReferencingName(value); + return this; + } + + public Embeddable withReplacesFields(Boolean value) { + setReplacesFields(value); + return this; + } + public Embeddable withFields(EmbeddableField... values) { if (values!= null) { for (EmbeddableField value: values) { @@ -99,6 +157,8 @@ public class Embeddable implements Serializable, XMLAppendable @Override public final void appendTo(XMLBuilder builder) { builder.append("name", name); + builder.append("referencingName", referencingName); + builder.append("replacesFields", replacesFields); builder.append("fields", "field", fields); } @@ -130,6 +190,24 @@ public class Embeddable implements Serializable, XMLAppendable return false; } } + if (referencingName == null) { + if (other.referencingName!= null) { + return false; + } + } else { + if (!referencingName.equals(other.referencingName)) { + return false; + } + } + if (replacesFields == null) { + if (other.replacesFields!= null) { + return false; + } + } else { + if (!replacesFields.equals(other.replacesFields)) { + return false; + } + } if (fields == null) { if (other.fields!= null) { return false; @@ -147,6 +225,8 @@ public class Embeddable implements Serializable, XMLAppendable final int prime = 31; int result = 1; result = ((prime*result)+((name == null)? 0 :name.hashCode())); + result = ((prime*result)+((referencingName == null)? 0 :referencingName.hashCode())); + result = ((prime*result)+((replacesFields == null)? 0 :replacesFields.hashCode())); result = ((prime*result)+((fields == null)? 0 :fields.hashCode())); return result; } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java index 48569b0207..072caa1ee0 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java @@ -190,8 +190,6 @@ public class Generate implements Serializable, XMLAppendable protected Boolean emptySchemas = false; @XmlElement(defaultValue = "true") protected Boolean javaTimeTypes = true; - @XmlElement(defaultValue = "false") - protected Boolean primaryKeyTypes = false; @XmlElement(defaultValue = "\\n") @XmlJavaTypeAdapter(StringAdapter.class) protected String newline = "\\n"; @@ -2092,30 +2090,6 @@ public class Generate implements Serializable, XMLAppendable this.javaTimeTypes = value; } - /** - * Whether wrapper types should be generated for primary key columns, and for their referencing foreign keys. - * - * @return - * possible object is - * {@link Boolean } - * - */ - public Boolean isPrimaryKeyTypes() { - return primaryKeyTypes; - } - - /** - * Sets the value of the primaryKeyTypes property. - * - * @param value - * allowed object is - * {@link Boolean } - * - */ - public void setPrimaryKeyTypes(Boolean value) { - this.primaryKeyTypes = value; - } - /** * The newline characters to be used in generated code. Whitespace characters can be used, e.g. \n, \r\n * @@ -2568,11 +2542,6 @@ public class Generate implements Serializable, XMLAppendable return this; } - public Generate withPrimaryKeyTypes(Boolean value) { - setPrimaryKeyTypes(value); - return this; - } - /** * The newline characters to be used in generated code. Whitespace characters can be used, e.g. \n, \r\n * @@ -2672,7 +2641,6 @@ public class Generate implements Serializable, XMLAppendable builder.append("emptyCatalogs", emptyCatalogs); builder.append("emptySchemas", emptySchemas); builder.append("javaTimeTypes", javaTimeTypes); - builder.append("primaryKeyTypes", primaryKeyTypes); builder.append("newline", newline); builder.append("indentation", indentation); } @@ -3407,15 +3375,6 @@ public class Generate implements Serializable, XMLAppendable return false; } } - if (primaryKeyTypes == null) { - if (other.primaryKeyTypes!= null) { - return false; - } - } else { - if (!primaryKeyTypes.equals(other.primaryKeyTypes)) { - return false; - } - } if (newline == null) { if (other.newline!= null) { return false; @@ -3520,7 +3479,6 @@ public class Generate implements Serializable, XMLAppendable result = ((prime*result)+((emptyCatalogs == null)? 0 :emptyCatalogs.hashCode())); result = ((prime*result)+((emptySchemas == null)? 0 :emptySchemas.hashCode())); result = ((prime*result)+((javaTimeTypes == null)? 0 :javaTimeTypes.hashCode())); - result = ((prime*result)+((primaryKeyTypes == null)? 0 :primaryKeyTypes.hashCode())); result = ((prime*result)+((newline == null)? 0 :newline.hashCode())); result = ((prime*result)+((indentation == null)? 0 :indentation.hashCode())); return result; diff --git a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.14.0.xsd b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.14.0.xsd index d549181ab0..817c860aee 100644 --- a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.14.0.xsd +++ b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.14.0.xsd @@ -793,6 +793,14 @@ This comparator can be used to influence the order of any object that is produce + + + + + + + + @@ -1005,12 +1013,20 @@ for Oracle.]]> - + + + + + + + + + @@ -1497,10 +1513,6 @@ source code generator, rather than JDBC's java.sql types.

This flag is ignored in the commercial Java 6 distribution of jOOQ 3.9+ ]]> - - - - diff --git a/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java b/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java index 60cde3c6e8..a105b8286f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java @@ -41,6 +41,7 @@ import org.jooq.Converter; import org.jooq.EmbeddableRecord; import org.jooq.Field; import org.jooq.Row; +import org.jooq.TableField; /** * A record implementation for a record originating from a single table @@ -61,6 +62,10 @@ public class EmbeddableRecordImpl> extends Abstrac super(fields); } + public EmbeddableRecordImpl(TableField... fields) { + super(fields); + } + @SuppressWarnings("unchecked") @Override public final R with(Field field, T value) { diff --git a/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java b/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java index 3a3dc28dd0..47727ec3fe 100644 --- a/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java +++ b/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java @@ -56,15 +56,17 @@ final class EmbeddableTableField extends Abs * Generated UID */ private static final long serialVersionUID = -7105430856294526440L; + final Class recordType; + final boolean replacesFields; final Table table; final TableField[] fields; - final Class recordType; - EmbeddableTableField(Name name, Class recordType, Table table, TableField[] fields) { + EmbeddableTableField(Name name, Class recordType, boolean replacesFields, Table table, TableField[] fields) { super(name, new DefaultDataType<>(SQLDialect.DEFAULT, recordType, name.last())); - this.table = table; this.recordType = recordType; + this.replacesFields = replacesFields; + this.table = table; this.fields = fields; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Internal.java b/jOOQ/src/main/java/org/jooq/impl/Internal.java index 0242b41444..e0c3db76e7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Internal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Internal.java @@ -42,6 +42,7 @@ import org.jooq.Check; import org.jooq.Converter; import org.jooq.DataType; import org.jooq.Domain; +import org.jooq.EmbeddableRecord; import org.jooq.ForeignKey; import org.jooq.Identity; import org.jooq.Index; @@ -76,9 +77,19 @@ public final class Internal { /** * Factory method for embeddable types. */ + @SafeVarargs @NotNull public static final TableField createEmbeddable(Name name, Class recordType, Table table, TableField... fields) { - return new EmbeddableTableField<>(name, recordType, table, fields); + return createEmbeddable(name, recordType, false, table, fields); + } + + /** + * Factory method for embeddable types. + */ + @SafeVarargs + @NotNull + public static final TableField createEmbeddable(Name name, Class recordType, boolean replacesFields, Table table, TableField... fields) { + return new EmbeddableTableField<>(name, recordType, replacesFields, table, fields); } /** @@ -123,6 +134,14 @@ public final class Internal { return new UniqueKeyImpl<>(table, name, fields, enforced); } + /** + * Factory method for unique keys. + */ + @NotNull + public static final > UniqueKey createUniqueKey(Table table, Name name, TableField embeddableField, boolean enforced) { + return createUniqueKey(table, name, fields(embeddableField), enforced); + } + /** * Factory method for foreign keys. * @@ -148,6 +167,14 @@ public final class Internal { return result; } + /** + * Factory method for foreign keys. + */ + @NotNull + public static final > ForeignKey createForeignKey(Table table, Name name, TableField fkEmbeddableField, UniqueKey uk, TableField ukEmbeddableField, boolean enforced) { + return createForeignKey(table, name, fields(fkEmbeddableField), uk, fields(ukEmbeddableField), enforced); + } + /** * Factory method for sequences. */ @@ -322,4 +349,12 @@ public final class Internal { public static final ForeignKey createForeignKey(UniqueKey key, Table table, String name, TableField[] fields, boolean enforced) { return createForeignKey(table, DSL.name(name), fields, key, key.getFieldsArray(), enforced); } + + /** + * Get the fields of an embeddable type. + */ + @NotNull + public static final > TableField[] fields(TableField embeddableField) { + return ((EmbeddableTableField) embeddableField).fields; + } }