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 b4d2002d8c..ba739929c0 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java @@ -80,6 +80,7 @@ import org.jooq.meta.ForeignKeyDefinition; import org.jooq.meta.IdentityDefinition; import org.jooq.meta.IndexDefinition; import org.jooq.meta.InverseForeignKeyDefinition; +import org.jooq.meta.ManyToManyKeyDefinition; import org.jooq.meta.PackageDefinition; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; @@ -292,13 +293,21 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { if (fk.getTable().getForeignKeys(referenced).size() == 1) return getJavaMethodName(referenced, mode); } - - if (definition instanceof InverseForeignKeyDefinition fk) { + else if (definition instanceof InverseForeignKeyDefinition fk) { TableDefinition referencing = fk.getReferencingTable(); if (fk.getTable().getInverseForeignKeys(referencing).size() == 1) return getJavaMethodName(referencing, mode); } + else if (definition instanceof ManyToManyKeyDefinition k) { + TableDefinition t1 = k.getForeignKey1().getReferencedTable(); + TableDefinition t2 = k.getForeignKey2().getReferencedTable(); + + if (t1.getManyToManyKeys(t2).size() == 1) + return getJavaMethodName(t2, mode); + else + return getJavaClassName0LC(k.getForeignKey2(), Mode.DEFAULT); + } } return getJavaClassName0LC(definition, Mode.DEFAULT); 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 8078726d56..5d42dbc970 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -180,6 +180,7 @@ import org.jooq.meta.IndexColumnDefinition; import org.jooq.meta.IndexDefinition; import org.jooq.meta.InverseForeignKeyDefinition; import org.jooq.meta.JavaTypeResolver; +import org.jooq.meta.ManyToManyKeyDefinition; import org.jooq.meta.PackageDefinition; import org.jooq.meta.ParameterDefinition; import org.jooq.meta.RoutineDefinition; @@ -6711,18 +6712,17 @@ public class JavaGenerator extends AbstractGenerator { } } - List inboundFKs = table.getInverseForeignKeys(); - if (inboundFKs.size() > 0 && generateGlobalKeyReferences()) { + if (generateImplicitJoinPathsToMany() && generateGlobalKeyReferences()) { + List inboundFKs = table.getInverseForeignKeys(); - // Inbound (to-many) implicit join paths - if (generateImplicitJoinPathsToMany()) { + if (inboundFKs.size() > 0) { Map pathCounts = inboundFKs.stream().collect(groupingBy(InverseForeignKeyDefinition::getReferencingTable, counting())); inboundFKLoop: for (InverseForeignKeyDefinition foreignKey : inboundFKs) { final String keyMethodName = out.ref(getStrategy().getJavaMethodName(foreignKey)); - if (outboundKeyMethodNames.contains(keyMethodName)) { + if (!outboundKeyMethodNames.add(keyMethodName)) { log.warn("Ambiguous key name", "The database object " + foreignKey.getQualifiedOutputName() + " generates an inbound key method name " + keyMethodName @@ -6755,7 +6755,6 @@ public class JavaGenerator extends AbstractGenerator { out.javadoc( "Get the implicit to-many join path to the " + foreignKey.getReferencingTable().getQualifiedName() + " table" + (pathCounts.get(foreignKey.getReferencingTable()) > 1 ? ", via the " + foreignKey.getInputName() + " key" : "") - + ".

EXPERIMENTAL! DO NOT USE THIS FEATURE YET." ); if (scala) { @@ -6784,6 +6783,55 @@ public class JavaGenerator extends AbstractGenerator { out.println("}"); } } + + List manyToManyKeys = table.getManyToManyKeys(); + Map pathCountsManytoMany = manyToManyKeys.stream().collect(groupingBy(d -> d.getForeignKey2().getReferencedTable(), counting())); + + manyToManyKeyLoop: + for (ManyToManyKeyDefinition manyToManyKey : manyToManyKeys) { + final String keyMethodName = out.ref(getStrategy().getJavaMethodName(manyToManyKey)); + + if (!outboundKeyMethodNames.add(keyMethodName)) { + log.warn("Ambiguous key name", + "The database object " + manyToManyKey.getQualifiedOutputName() + + " generates an inbound key method name " + keyMethodName + + " which conflicts with the previously generated outbound key method name." + + " Use a custom generator strategy to disambiguate the types. More information here:\n" + + " - https://www.jooq.org/doc/latest/manual/code-generation/codegen-generatorstrategy/\n" + + " - https://www.jooq.org/doc/latest/manual/code-generation/codegen-matcherstrategy/" + ); + continue manyToManyKeyLoop; + } + + final String key1MethodName = out.ref(getStrategy().getJavaMethodName(manyToManyKey.getForeignKey1().getInverse())); + final String key2MethodName = out.ref(getStrategy().getJavaMethodName(manyToManyKey.getForeignKey2())); + final TableDefinition referencedTable = manyToManyKey.getForeignKey2().getReferencedTable(); + final String referencedTableClassName = out.ref(getStrategy().getFullJavaClassName(referencedTable)); + + out.javadoc( + "Get the implicit many-to-many join path to the " + referencedTable.getQualifiedName() + " table" + + (pathCountsManytoMany.get(referencedTable) > 1 ? ", via the " + manyToManyKey.getInputName() + " key" : "") + ); + + if (scala) { + out.println("%sdef %s: %s = %s().%s()", visibility(), scalaWhitespaceSuffix(keyMethodName), referencedTableClassName, key1MethodName, key2MethodName); + } + else if (kotlin) { + if (generateImplicitJoinPathsAsKotlinProperties()) { + out.println("%sval %s: %s", visibility(), keyMethodName, referencedTableClassName); + out.tab(1).println("get(): %s = %s().%s()", referencedTableClassName, key1MethodName, key2MethodName); + } + else { + out.println(); + out.println("%sfun %s(): %s = %s().%s()", visibility(), keyMethodName, referencedTableClassName, key1MethodName, key2MethodName); + } + } + else { + out.println("%s%s %s() {", visibility(), referencedTableClassName, keyMethodName); + out.println("return %s().%s();", key1MethodName, key2MethodName); + out.println("}"); + } + } } } } 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 9d659ad25a..0ea1764d8b 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java @@ -43,7 +43,9 @@ import static org.jooq.impl.DSL.table; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.jooq.Record; import org.jooq.Table; @@ -166,7 +168,7 @@ implements for (UniqueKeyDefinition uk : getKeys()) for (ForeignKeyDefinition fk : uk.getForeignKeys()) - result.add(fk.inverse()); + result.add(fk.getInverse()); return result; } @@ -182,6 +184,52 @@ implements return result; } + @Override + public final List getManyToManyKeys() { + List result = new ArrayList<>(); + + for (InverseForeignKeyDefinition k : getInverseForeignKeys()) { + + ukLoop: + for (UniqueKeyDefinition uk : k.getReferencingTable().getKeys()) { + if (!uk.getKeyColumns().containsAll(k.getReferencingColumns())) + continue ukLoop; + + fkLoop: + for (ForeignKeyDefinition fk : k.getReferencingTable().getForeignKeys()) { + if (!k.getForeignKey().equals(fk)) { + Set columns = new HashSet<>(); + columns.addAll(k.getForeignKey().getKeyColumns()); + + // [#13639] The two FKs must not overlap + for (ColumnDefinition c : fk.getKeyColumns()) + if (!columns.add(c)) + continue fkLoop; + + // [#13639] Require UK = FK1 + FK2 + if (!columns.equals(new HashSet<>(uk.getKeyColumns()))) + continue fkLoop; + + result.add(new DefaultManyToManyKeyDefinition(k.getForeignKey(), uk, fk)); + } + } + } + } + + return result; + } + + @Override + public final List getManyToManyKeys(TableDefinition referencing) { + List result = new ArrayList<>(); + + for (ManyToManyKeyDefinition key : getManyToManyKeys()) + if (referencing.equals(key.getForeignKey2().getReferencedTable())) + result.add(key); + + return result; + } + @Override public final List getCheckConstraints() { return getDatabase().getRelations().getCheckConstraints(this); 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 dc80877bac..7a26b19e27 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java @@ -102,7 +102,7 @@ public class DefaultForeignKeyDefinition extends AbstractConstraintDefinition im } @Override - public InverseForeignKeyDefinition inverse() { + public InverseForeignKeyDefinition getInverse() { return new DefaultInverseForeignKeyDefinition(this); } } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultInverseForeignKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultInverseForeignKeyDefinition.java index d0c2c88f73..228fbb3d21 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultInverseForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultInverseForeignKeyDefinition.java @@ -80,7 +80,7 @@ public class DefaultInverseForeignKeyDefinition extends AbstractDefinition imple } @Override - public ForeignKeyDefinition foreignKey() { + public ForeignKeyDefinition getForeignKey() { return foreignKey; } } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultManyToManyKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultManyToManyKeyDefinition.java new file mode 100644 index 0000000000..a0156a23af --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultManyToManyKeyDefinition.java @@ -0,0 +1,82 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.meta; + +import java.util.List; + +/** + * @author Lukas Eder + */ +public class DefaultManyToManyKeyDefinition extends AbstractConstraintDefinition implements ManyToManyKeyDefinition { + + private final ForeignKeyDefinition foreignKey1; + private final UniqueKeyDefinition uniqueKey; + private final ForeignKeyDefinition foreignKey2; + + public DefaultManyToManyKeyDefinition( + ForeignKeyDefinition foreignKey1, + UniqueKeyDefinition uniqueKey, + ForeignKeyDefinition foreignKey2 + ) { + super(uniqueKey.getSchema(), uniqueKey.getTable(), uniqueKey.getName(), uniqueKey.enforced()); + + this.foreignKey1 = foreignKey1; + this.uniqueKey = uniqueKey; + this.foreignKey2 = foreignKey2; + } + + @Override + public UniqueKeyDefinition getUniqueKey() { + return uniqueKey; + } + + @Override + public List getKeyColumns() { + return uniqueKey.getKeyColumns(); + } + + @Override + public ForeignKeyDefinition getForeignKey1() { + return foreignKey1; + } + + @Override + public ForeignKeyDefinition getForeignKey2() { + return foreignKey2; + } +} 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 f3c084a494..18c23afae1 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java @@ -92,5 +92,5 @@ public interface ForeignKeyDefinition extends ConstraintDefinition { /** * Get the inverse key. */ - InverseForeignKeyDefinition inverse(); + InverseForeignKeyDefinition getInverse(); } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java index 9123f9e9df..d6ef07da2e 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java @@ -41,6 +41,9 @@ import java.util.List; /** * An object holding information about an inverse foreign key relationship. + *

+ * This constraint represents the {@link UniqueKeyDefinition} that enforces the + * uniqueness at the referenced end of the {@link ForeignKeyDefinition}. * * @author Lukas Eder */ @@ -64,5 +67,5 @@ public interface InverseForeignKeyDefinition extends ConstraintDefinition { /** * Get the foreign key this is an inverse of. */ - ForeignKeyDefinition foreignKey(); + ForeignKeyDefinition getForeignKey(); } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/ManyToManyKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/ManyToManyKeyDefinition.java new file mode 100644 index 0000000000..440ac757f8 --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ManyToManyKeyDefinition.java @@ -0,0 +1,75 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.meta; + +import java.util.List; + +/** + * An object holding information about an many to many foreign key relationship. + *

+ * This constraint represents the {@link UniqueKeyDefinition} that enforces the + * relationship in the relationship table. The {@link UniqueKeyDefinition} is a + * composite key, whose columns are completely contained in the two outbound + * {@link ForeignKeyDefinition} specified by {@link #getForeignKey1()} and + * {@link #getForeignKey2()}. The order of {@link ForeignKeyDefinition} depends + * on the navigation direction. + * + * @author Lukas Eder + */ +public interface ManyToManyKeyDefinition extends ConstraintDefinition { + + /** + * The list of columns making up the unique key. + */ + List getKeyColumns(); + + /** + * The unique key that enforces the relationship. + */ + UniqueKeyDefinition getUniqueKey(); + + /** + * Get the first foreign key in the relationship. + */ + ForeignKeyDefinition getForeignKey1(); + + /** + * Get the second foreign key in the relationship. + */ + ForeignKeyDefinition getForeignKey2(); +} 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 c08d9a74a5..9768ad65b5 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java @@ -138,6 +138,17 @@ public interface TableDefinition extends Definition { */ List getInverseForeignKeys(TableDefinition referencing); + /** + * Get the many to many keys for this table. + */ + List getManyToManyKeys(); + + /** + * Get the many to many keys for this table referencing another, specific + * table. + */ + List getManyToManyKeys(TableDefinition referencing); + /** * Get the CHECK constraints for this table. */