[jOOQ/jOOQ#14985] Support many-to-many path joins
This commit is contained in:
parent
d5577c20db
commit
caa3dca7db
@ -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);
|
||||
|
||||
@ -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<InverseForeignKeyDefinition> inboundFKs = table.getInverseForeignKeys();
|
||||
if (inboundFKs.size() > 0 && generateGlobalKeyReferences()) {
|
||||
if (generateImplicitJoinPathsToMany() && generateGlobalKeyReferences()) {
|
||||
List<InverseForeignKeyDefinition> inboundFKs = table.getInverseForeignKeys();
|
||||
|
||||
// Inbound (to-many) implicit join paths
|
||||
if (generateImplicitJoinPathsToMany()) {
|
||||
if (inboundFKs.size() > 0) {
|
||||
Map<TableDefinition, Long> 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 <code>" + foreignKey.getReferencingTable().getQualifiedName() + "</code> table"
|
||||
+ (pathCounts.get(foreignKey.getReferencingTable()) > 1 ? ", via the <code>" + foreignKey.getInputName() + "</code> key" : "")
|
||||
+ ".<p><strong>EXPERIMENTAL! DO NOT USE THIS FEATURE YET.</strong>"
|
||||
);
|
||||
|
||||
if (scala) {
|
||||
@ -6784,6 +6783,55 @@ public class JavaGenerator extends AbstractGenerator {
|
||||
out.println("}");
|
||||
}
|
||||
}
|
||||
|
||||
List<ManyToManyKeyDefinition> manyToManyKeys = table.getManyToManyKeys();
|
||||
Map<TableDefinition, Long> 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 <code>" + referencedTable.getQualifiedName() + "</code> table"
|
||||
+ (pathCountsManytoMany.get(referencedTable) > 1 ? ", via the <code>" + manyToManyKey.getInputName() + "</code> 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("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ManyToManyKeyDefinition> getManyToManyKeys() {
|
||||
List<ManyToManyKeyDefinition> 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<ColumnDefinition> 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<ManyToManyKeyDefinition> getManyToManyKeys(TableDefinition referencing) {
|
||||
List<ManyToManyKeyDefinition> result = new ArrayList<>();
|
||||
|
||||
for (ManyToManyKeyDefinition key : getManyToManyKeys())
|
||||
if (referencing.equals(key.getForeignKey2().getReferencedTable()))
|
||||
result.add(key);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<CheckConstraintDefinition> getCheckConstraints() {
|
||||
return getDatabase().getRelations().getCheckConstraints(this);
|
||||
|
||||
@ -102,7 +102,7 @@ public class DefaultForeignKeyDefinition extends AbstractConstraintDefinition im
|
||||
}
|
||||
|
||||
@Override
|
||||
public InverseForeignKeyDefinition inverse() {
|
||||
public InverseForeignKeyDefinition getInverse() {
|
||||
return new DefaultInverseForeignKeyDefinition(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ public class DefaultInverseForeignKeyDefinition extends AbstractDefinition imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForeignKeyDefinition foreignKey() {
|
||||
public ForeignKeyDefinition getForeignKey() {
|
||||
return foreignKey;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ColumnDefinition> getKeyColumns() {
|
||||
return uniqueKey.getKeyColumns();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForeignKeyDefinition getForeignKey1() {
|
||||
return foreignKey1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForeignKeyDefinition getForeignKey2() {
|
||||
return foreignKey2;
|
||||
}
|
||||
}
|
||||
@ -92,5 +92,5 @@ public interface ForeignKeyDefinition extends ConstraintDefinition {
|
||||
/**
|
||||
* Get the inverse key.
|
||||
*/
|
||||
InverseForeignKeyDefinition inverse();
|
||||
InverseForeignKeyDefinition getInverse();
|
||||
}
|
||||
|
||||
@ -41,6 +41,9 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* An object holding information about an inverse foreign key relationship.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
|
||||
@ -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.
|
||||
* <p>
|
||||
* 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<ColumnDefinition> 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();
|
||||
}
|
||||
@ -138,6 +138,17 @@ public interface TableDefinition extends Definition {
|
||||
*/
|
||||
List<InverseForeignKeyDefinition> getInverseForeignKeys(TableDefinition referencing);
|
||||
|
||||
/**
|
||||
* Get the many to many keys for this table.
|
||||
*/
|
||||
List<ManyToManyKeyDefinition> getManyToManyKeys();
|
||||
|
||||
/**
|
||||
* Get the many to many keys for this table referencing another, specific
|
||||
* table.
|
||||
*/
|
||||
List<ManyToManyKeyDefinition> getManyToManyKeys(TableDefinition referencing);
|
||||
|
||||
/**
|
||||
* Get the <code>CHECK</code> constraints for this table.
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user