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 c2afc97d44..74e4f95afb 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java @@ -70,6 +70,7 @@ abstract class AbstractGenerator implements Generator { boolean generateIndexes = true; boolean generateRelations = true; boolean generateImplicitJoinPathsToOne = true; + boolean generateImplicitJoinPathsToMany = true; boolean generateImplicitJoinPathsAsKotlinProperties = true; boolean generateInstanceFields = true; VisibilityModifier generateVisibilityModifier = VisibilityModifier.DEFAULT; @@ -300,6 +301,16 @@ abstract class AbstractGenerator implements Generator { this.generateImplicitJoinPathsToOne = generateImplicitJoinPathsToOne; } + @Override + public boolean generateImplicitJoinPathsToMany() { + return generateImplicitJoinPathsToMany && generateRelations(); + } + + @Override + public void setGenerateImplicitJoinPathsToMany(boolean generateImplicitJoinPathsToMany) { + this.generateImplicitJoinPathsToMany = generateImplicitJoinPathsToMany; + } + @Override public boolean generateImplicitJoinPathsAsKotlinProperties() { return generateImplicitJoinPathsAsKotlinProperties && generateImplicitJoinPathsToOne(); 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 b0aa51b07f..b4d2002d8c 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java @@ -79,6 +79,7 @@ import org.jooq.meta.EnumDefinition; import org.jooq.meta.ForeignKeyDefinition; import org.jooq.meta.IdentityDefinition; import org.jooq.meta.IndexDefinition; +import org.jooq.meta.InverseForeignKeyDefinition; import org.jooq.meta.PackageDefinition; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; @@ -284,11 +285,20 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { public String getJavaMethodName(Definition definition, Mode mode) { // [#7148] If table A references table B only once, then B is the ideal name // for the implicit JOIN path. Otherwise, fall back to the foreign key name - if (getUseTableNameForUnambiguousFKs() && definition instanceof ForeignKeyDefinition fk) { - TableDefinition referenced = fk.getReferencedTable(); + if (getUseTableNameForUnambiguousFKs()) { + if (definition instanceof ForeignKeyDefinition fk) { + TableDefinition referenced = fk.getReferencedTable(); - if (fk.getKeyTable().getForeignKeys(referenced).size() == 1) - return getJavaMethodName(referenced, mode); + if (fk.getTable().getForeignKeys(referenced).size() == 1) + return getJavaMethodName(referenced, mode); + } + + if (definition instanceof InverseForeignKeyDefinition fk) { + TableDefinition referencing = fk.getReferencingTable(); + + if (fk.getTable().getInverseForeignKeys(referencing).size() == 1) + return getJavaMethodName(referencing, mode); + } } return getJavaClassName0LC(definition, Mode.DEFAULT); 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 816dbe2915..9835a21fa9 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java @@ -718,8 +718,10 @@ public class GenerationTool { generator.setGenerateIndexes(g.getGenerate().isIndexes()); if (g.getGenerate().isRelations() != null) generator.setGenerateRelations(g.getGenerate().isRelations()); - if (g.getGenerate().isImplicitJoinPathsToOne() != null) + if (g.getGenerate().isImplicitJoinPathsToMany() != null) generator.setGenerateImplicitJoinPathsToOne(g.getGenerate().isImplicitJoinPathsToOne()); + if (g.getGenerate().isImplicitJoinPathsToOne() != null) + generator.setGenerateImplicitJoinPathsToMany(g.getGenerate().isImplicitJoinPathsToMany()); if (g.getGenerate().isImplicitJoinPathsAsKotlinProperties() != null) generator.setGenerateImplicitJoinPathsAsKotlinProperties(g.getGenerate().isImplicitJoinPathsAsKotlinProperties()); if (g.getGenerate().isDeprecated() != 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 f8148a0ef0..f90c19b6e5 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java @@ -131,6 +131,18 @@ public interface Generator { */ void setGenerateImplicitJoinPathsToOne(boolean generateImplicitJoinPathsToOne); + /** + * Whether implicit join path constructors on generated tables for incoming + * foreign key relationships (to-many relationships) should be generated. + */ + boolean generateImplicitJoinPathsToMany(); + + /** + * Whether implicit join path constructors on generated tables for incoming + * foreign key relationships (to-many relationships) should be generated. + */ + void setGenerateImplicitJoinPathsToMany(boolean generateImplicitJoinPathsToMany); + /** * Whether implicit join path constructors should be offered as properties * in Kotlin. 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 09a80379e4..4308b0e2c3 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -108,6 +108,7 @@ import org.jooq.ForeignKey; import org.jooq.Generated; import org.jooq.Identity; import org.jooq.Index; +import org.jooq.InverseForeignKey; // ... import org.jooq.Name; import org.jooq.OrderField; @@ -177,6 +178,7 @@ import org.jooq.meta.ForeignKeyDefinition; import org.jooq.meta.IdentityDefinition; import org.jooq.meta.IndexColumnDefinition; import org.jooq.meta.IndexDefinition; +import org.jooq.meta.InverseForeignKeyDefinition; import org.jooq.meta.JavaTypeResolver; import org.jooq.meta.PackageDefinition; import org.jooq.meta.ParameterDefinition; @@ -6352,21 +6354,41 @@ public class JavaGenerator extends AbstractGenerator { out.println("}"); } - if (generateImplicitJoinPathsToOne() && generateGlobalKeyReferences() && !table.isTableValuedFunction()) { - out.println(); + if (generateGlobalKeyReferences() && !table.isTableValuedFunction()) { + if (generateImplicitJoinPathsToOne()) { + out.println(); - if (scala) { - out.println("%sdef this(child: %s[_ <: %s], key: %s[_ <: %s, %s]) = this(%s.createPathAlias(child, key), child, key, %s, null)", - visibility(), Table.class, Record.class, ForeignKey.class, Record.class, recordType, Internal.class, tableId); + if (scala) { + out.println("%sdef this(child: %s[_ <: %s], key: %s[_ <: %s, %s]) = this(%s.createPathAlias(child, key), child, key, %s, null)", + visibility(), Table.class, Record.class, ForeignKey.class, Record.class, recordType, Internal.class, tableId); + } + else if (kotlin) { + out.println("%sconstructor(child: %s, key: %s): this(%s.createPathAlias(child, key), child, key, %s, null)", + visibility(), Table.class, Record.class, ForeignKey.class, Record.class, recordType, Internal.class, tableId); + } + else { + out.println("%s %s(%s child, %s key) {", visibility(), Record.class, className, Table.class, ForeignKey.class, recordType); + out.println("super(child, key, %s);", tableId); + out.println("}"); + } } - else if (kotlin) { - out.println("%sconstructor(child: %s, key: %s): this(%s.createPathAlias(child, key), child, key, %s, null)", - visibility(), Table.class, Record.class, ForeignKey.class, Record.class, recordType, Internal.class, tableId); - } - else { - out.println("%s %s(%s child, %s key) {", visibility(), Record.class, className, Table.class, ForeignKey.class, recordType); - out.println("super(child, key, %s);", tableId); - out.println("}"); + + if (generateImplicitJoinPathsToMany()) { + out.println(); + + if (scala) { + out.println("%sdef this(parent: %s[_ <: %s], key: %s[_ <: %s, %s]) = this(%s.createPathAlias(parent, key), parent, key, %s, null)", + visibility(), Table.class, Record.class, InverseForeignKey.class, Record.class, recordType, Internal.class, tableId); + } + else if (kotlin) { + out.println("%sconstructor(parent: %s, key: %s): this(%s.createPathAlias(parent, key), parent, key, %s, null)", + visibility(), Table.class, Record.class, InverseForeignKey.class, Record.class, recordType, Internal.class, tableId); + } + else { + out.println("%s %s(%s parent, %s key) {", visibility(), Record.class, className, Table.class, InverseForeignKey.class, recordType); + out.println("super(parent, key, %s);", tableId); + out.println("}"); + } } } @@ -6597,13 +6619,13 @@ public class JavaGenerator extends AbstractGenerator { } // Foreign keys - List foreignKeys = table.getForeignKeys(); + List outboundFKs = table.getForeignKeys(); // [#7554] [#8028] Not yet supported with global key references turned off - if (foreignKeys.size() > 0 && generateGlobalKeyReferences()) { + if (outboundFKs.size() > 0 && generateGlobalKeyReferences()) { final List keyFullIds = kotlin - ? out.ref(getStrategy().getFullJavaIdentifiers(foreignKeys)) - : out.ref(getStrategy().getFullJavaIdentifiers(foreignKeys), 2); + ? out.ref(getStrategy().getFullJavaIdentifiers(outboundFKs)) + : out.ref(getStrategy().getFullJavaIdentifiers(outboundFKs), 2); if (scala) { out.println(); @@ -6629,7 +6651,7 @@ public class JavaGenerator extends AbstractGenerator { out.println(); // [#8762] Cache these calls for much improved runtime performance! - for (ForeignKeyDefinition foreignKey : foreignKeys) { + for (ForeignKeyDefinition foreignKey : outboundFKs) { final String referencedTableClassName = out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencedTable())); final String keyMethodName = out.ref(getStrategy().getJavaMethodName(foreignKey)); @@ -6643,8 +6665,8 @@ public class JavaGenerator extends AbstractGenerator { } } - Map pathCounts = foreignKeys.stream().collect(groupingBy(ForeignKeyDefinition::getReferencedTable, counting())); - for (ForeignKeyDefinition foreignKey : foreignKeys) { + Map pathCounts = outboundFKs.stream().collect(groupingBy(ForeignKeyDefinition::getReferencedTable, counting())); + for (ForeignKeyDefinition foreignKey : outboundFKs) { final String keyFullId = kotlin ? out.ref(getStrategy().getFullJavaIdentifier(foreignKey)) : out.ref(getStrategy().getFullJavaIdentifier(foreignKey), 2); @@ -6686,6 +6708,74 @@ public class JavaGenerator extends AbstractGenerator { } } } + + List inboundFKs = table.getInverseForeignKeys(); + if (inboundFKs.size() > 0 && generateGlobalKeyReferences()) { + + // Inbound (to-many) implicit join paths + if (generateImplicitJoinPathsToMany()) { + if (scala) {} + else { + out.println(); + + // [#8762] Cache these calls for much improved runtime performance! + for (InverseForeignKeyDefinition foreignKey : inboundFKs) { + final String referencingTableClassName = out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencingTable())); + final String keyMethodName = out.ref(getStrategy().getJavaMethodName(foreignKey)); + + // [#13008] Prevent conflicts with the below leading underscore + final String unquotedKeyMethodName = keyMethodName.replace("`", ""); + + if (kotlin) + out.println("private lateinit var _%s: %s", unquotedKeyMethodName, referencingTableClassName); + else + out.println("private transient %s _%s;", referencingTableClassName, keyMethodName); + } + } + + Map pathCounts = inboundFKs.stream().collect(groupingBy(InverseForeignKeyDefinition::getReferencingTable, counting())); + for (InverseForeignKeyDefinition foreignKey : inboundFKs) { + final String keyFullId = kotlin + ? out.ref(getStrategy().getFullJavaIdentifier(foreignKey)) + : out.ref(getStrategy().getFullJavaIdentifier(foreignKey), 2); + final String referencingTableClassName = out.ref(getStrategy().getFullJavaClassName(foreignKey.getReferencingTable())); + final String keyMethodName = out.ref(getStrategy().getJavaMethodName(foreignKey)); + final String unquotedKeyMethodName = keyMethodName.replace("`", ""); + + 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) { + out.println("%slazy val %s: %s = { new %s(this, %s.getInverseKey()) }", visibility(), scalaWhitespaceSuffix(keyMethodName), referencingTableClassName, referencingTableClassName, keyFullId); + } + else if (kotlin) { + out.println("%sfun %s(): %s {", visibility(), keyMethodName, referencingTableClassName); + out.println("if (!this::_%s.isInitialized)", unquotedKeyMethodName); + out.println("_%s = %s(this, %s.inverseKey)", unquotedKeyMethodName, referencingTableClassName, keyFullId); + out.println(); + out.println("return _%s;", unquotedKeyMethodName); + out.println("}"); + + if (generateImplicitJoinPathsAsKotlinProperties()) { + out.println(); + out.println("%sval %s: %s", visibility(), keyMethodName, referencingTableClassName); + out.tab(1).println("get(): %s = %s()", referencingTableClassName, keyMethodName); + } + } + else { + out.println("%s%s %s() {", visibility(), referencingTableClassName, keyMethodName); + out.println("if (_%s == null)", keyMethodName); + out.println("_%s = new %s(this, %s.getInverseKey());", keyMethodName, referencingTableClassName, keyFullId); + out.println(); + out.println("return _%s;", keyMethodName); + out.println("}"); + } + } + } + } } List cc = table.getCheckConstraints(); 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 5669be47cd..9d659ad25a 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java @@ -160,6 +160,28 @@ implements return result; } + @Override + public final List getInverseForeignKeys() { + List result = new ArrayList<>(); + + for (UniqueKeyDefinition uk : getKeys()) + for (ForeignKeyDefinition fk : uk.getForeignKeys()) + result.add(fk.inverse()); + + return result; + } + + @Override + public final List getInverseForeignKeys(TableDefinition referencing) { + List result = new ArrayList<>(); + + for (InverseForeignKeyDefinition key : getInverseForeignKeys()) + if (referencing.equals(key.getReferencingTable())) + 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 84cba1c41a..dc80877bac 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultForeignKeyDefinition.java @@ -100,4 +100,9 @@ public class DefaultForeignKeyDefinition extends AbstractConstraintDefinition im return keys.size(); } + + @Override + public InverseForeignKeyDefinition inverse() { + 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 new file mode 100644 index 0000000000..d0c2c88f73 --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultInverseForeignKeyDefinition.java @@ -0,0 +1,86 @@ +/* + * 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; + +import org.jooq.Name; + +/** + * @author Lukas Eder + */ +public class DefaultInverseForeignKeyDefinition extends AbstractDefinition implements InverseForeignKeyDefinition { + + private final ForeignKeyDefinition foreignKey; + + public DefaultInverseForeignKeyDefinition(ForeignKeyDefinition foreignKey) { + super(foreignKey.getDatabase(), foreignKey.getSchema(), foreignKey.getName()); + + this.foreignKey = foreignKey; + } + + @Override + public TableDefinition getTable() { + return foreignKey.getReferencedTable(); + } + + @Override + public boolean enforced() { + return foreignKey.enforced(); + } + + @Override + public List getKeyColumns() { + return foreignKey.getReferencedColumns(); + } + + @Override + public TableDefinition getReferencingTable() { + return foreignKey.getTable(); + } + + @Override + public List getReferencingColumns() { + return foreignKey.getKeyColumns(); + } + + @Override + public ForeignKeyDefinition foreignKey() { + return foreignKey; + } +} 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 7b434b6266..f3c084a494 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ForeignKeyDefinition.java @@ -88,4 +88,9 @@ public interface ForeignKeyDefinition extends ConstraintDefinition { * Count the number of references between referencing and referenced tables. */ int countSimilarReferences(); + + /** + * Get the inverse key. + */ + InverseForeignKeyDefinition inverse(); } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java new file mode 100644 index 0000000000..9123f9e9df --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/InverseForeignKeyDefinition.java @@ -0,0 +1,68 @@ +/* + * 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 inverse foreign key relationship. + * + * @author Lukas Eder + */ +public interface InverseForeignKeyDefinition extends ConstraintDefinition { + + /** + * The list of columns making up the unique key. + */ + List getKeyColumns(); + + /** + * The definition of the referencing table. + */ + TableDefinition getReferencingTable(); + + /** + * The list of columns referencing this unique key from the foreign key. + */ + List getReferencingColumns(); + + /** + * Get the foreign key this is an inverse of. + */ + ForeignKeyDefinition foreignKey(); +} 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 1566863c51..c08d9a74a5 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java @@ -128,6 +128,16 @@ public interface TableDefinition extends Definition { */ List getForeignKeys(TableDefinition referenced); + /** + * Get the inverse foreign keys for this table. + */ + List getInverseForeignKeys(); + + /** + * Get the inverse foreign keys for this table referenced from a specific table. + */ + List getInverseForeignKeys(TableDefinition referencing); + /** * Get the CHECK constraints for this table. */ 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 4dd63ead90..460b7476f2 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 @@ -38,6 +38,8 @@ public class Generate implements Serializable, XMLAppendable protected Boolean sequenceFlags = true; @XmlElement(defaultValue = "true") protected Boolean implicitJoinPathsToOne = true; + @XmlElement(defaultValue = "false") + protected Boolean implicitJoinPathsToMany = false; @XmlElement(defaultValue = "true") protected Boolean implicitJoinPathsUseTableNameForUnambiguousFKs = true; @XmlElement(defaultValue = "true") @@ -336,6 +338,30 @@ public class Generate implements Serializable, XMLAppendable this.implicitJoinPathsToOne = value; } + /** + * Generate implicit join path constructors on generated tables for incoming foreign key relationships (to-many relationships)

EXPERIMENTAL functionality! Do not use this feature, yet + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isImplicitJoinPathsToMany() { + return implicitJoinPathsToMany; + } + + /** + * Sets the value of the implicitJoinPathsToMany property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setImplicitJoinPathsToMany(Boolean value) { + this.implicitJoinPathsToMany = value; + } + /** * Whether names of unambiguous {@link org.jooq.meta.ForeignKeyDefinition} should be based * on the referenced {@link org.jooq.meta.TableDefinition}. @@ -2637,6 +2663,11 @@ public class Generate implements Serializable, XMLAppendable return this; } + public Generate withImplicitJoinPathsToMany(Boolean value) { + setImplicitJoinPathsToMany(value); + return this; + } + public Generate withImplicitJoinPathsUseTableNameForUnambiguousFKs(Boolean value) { setImplicitJoinPathsUseTableNameForUnambiguousFKs(value); return this; @@ -3181,6 +3212,7 @@ public class Generate implements Serializable, XMLAppendable builder.append("relations", relations); builder.append("sequenceFlags", sequenceFlags); builder.append("implicitJoinPathsToOne", implicitJoinPathsToOne); + builder.append("implicitJoinPathsToMany", implicitJoinPathsToMany); builder.append("implicitJoinPathsUseTableNameForUnambiguousFKs", implicitJoinPathsUseTableNameForUnambiguousFKs); builder.append("implicitJoinPathsAsKotlinProperties", implicitJoinPathsAsKotlinProperties); builder.append("deprecated", deprecated); @@ -3335,6 +3367,15 @@ public class Generate implements Serializable, XMLAppendable return false; } } + if (implicitJoinPathsToMany == null) { + if (other.implicitJoinPathsToMany!= null) { + return false; + } + } else { + if (!implicitJoinPathsToMany.equals(other.implicitJoinPathsToMany)) { + return false; + } + } if (implicitJoinPathsUseTableNameForUnambiguousFKs == null) { if (other.implicitJoinPathsUseTableNameForUnambiguousFKs!= null) { return false; @@ -4219,6 +4260,7 @@ public class Generate implements Serializable, XMLAppendable result = ((prime*result)+((relations == null)? 0 :relations.hashCode())); result = ((prime*result)+((sequenceFlags == null)? 0 :sequenceFlags.hashCode())); result = ((prime*result)+((implicitJoinPathsToOne == null)? 0 :implicitJoinPathsToOne.hashCode())); + result = ((prime*result)+((implicitJoinPathsToMany == null)? 0 :implicitJoinPathsToMany.hashCode())); result = ((prime*result)+((implicitJoinPathsUseTableNameForUnambiguousFKs == null)? 0 :implicitJoinPathsUseTableNameForUnambiguousFKs.hashCode())); result = ((prime*result)+((implicitJoinPathsAsKotlinProperties == null)? 0 :implicitJoinPathsAsKotlinProperties.hashCode())); result = ((prime*result)+((deprecated == null)? 0 :deprecated.hashCode())); diff --git a/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd b/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd index 274c0422be..fe02a72ebc 100644 --- a/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd +++ b/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd @@ -1951,6 +1951,10 @@ This is a prerequisite for various advanced features]]> + + + EXPERIMENTAL functionality! Do not use this feature, yet]]> + The FOREIGN KEY's owner table record - * @param The referenced KEY's owner table record + * @param The FOREIGN KEY's owner table record + * @param The referenced KEY's owner table record * @author Lukas Eder */ @SuppressWarnings("unchecked") -public interface ForeignKey extends Key { +public interface ForeignKey extends Key { + + /** + * The inverse key. + */ + @NotNull + InverseForeignKey getInverseKey(); /** * The referenced UniqueKey. */ @NotNull - UniqueKey getKey(); + UniqueKey getKey(); /** * The fields that make up the referenced UniqueKey. @@ -74,7 +80,7 @@ public interface ForeignKey extends Key { * {@link UniqueKey#getFields()}, but not necessarily so. */ @NotNull - List> getKeyFields(); + List> getKeyFields(); /** * The fields that make up the referenced UniqueKey. @@ -86,7 +92,7 @@ public interface ForeignKey extends Key { * @see #getKeyFields() */ @NotNull - TableField @NotNull [] getKeyFieldsArray(); + TableField @NotNull [] getKeyFieldsArray(); /** * Fetch a parent record of a given record through this foreign key @@ -100,7 +106,7 @@ public interface ForeignKey extends Key { */ @Nullable @Blocking - O fetchParent(R record) throws DataAccessException; + PARENT fetchParent(CHILD record) throws DataAccessException; /** * Fetch parent records of a given set of record through this foreign key @@ -114,7 +120,7 @@ public interface ForeignKey extends Key { */ @NotNull @Blocking - Result fetchParents(R... records) throws DataAccessException; + Result fetchParents(CHILD... records) throws DataAccessException; /** * Fetch parent records of a given set of record through this foreign key @@ -128,7 +134,7 @@ public interface ForeignKey extends Key { */ @NotNull @Blocking - Result fetchParents(Collection records) throws DataAccessException; + Result fetchParents(Collection records) throws DataAccessException; /** * Fetch child records of a given record through this foreign key @@ -142,7 +148,7 @@ public interface ForeignKey extends Key { */ @NotNull @Blocking - Result fetchChildren(O record) throws DataAccessException; + Result fetchChildren(PARENT record) throws DataAccessException; /** * Fetch child records of a given set of records through this foreign key @@ -157,7 +163,7 @@ public interface ForeignKey extends Key { */ @NotNull @Blocking - Result fetchChildren(O... records) throws DataAccessException; + Result fetchChildren(PARENT... records) throws DataAccessException; /** * Fetch child records of a given set of records through this foreign key @@ -172,47 +178,47 @@ public interface ForeignKey extends Key { */ @NotNull @Blocking - Result fetchChildren(Collection records) throws DataAccessException; + Result fetchChildren(Collection records) throws DataAccessException; /** * Get a table expression representing the parent of a record, given this * foreign key. */ @NotNull - Table parent(R record); + Table parent(CHILD record); /** * Get a table expression representing the parents of a record, given this * foreign key. */ @NotNull - Table parents(R... records); + Table parents(CHILD... records); /** * Get a table expression representing the parents of a record, given this * foreign key. */ @NotNull - Table parents(Collection records); + Table parents(Collection records); /** * Get a table expression representing the children of a record, given this * foreign key. */ @NotNull - Table children(O record); + Table children(PARENT record); /** * Get a table expression representing the children of a record, given this * foreign key. */ @NotNull - Table children(O... records); + Table children(PARENT... records); /** * Get a table expression representing the children of a record, given this * foreign key. */ @NotNull - Table children(Collection records); + Table children(Collection records); } diff --git a/jOOQ/src/main/java/org/jooq/InverseForeignKey.java b/jOOQ/src/main/java/org/jooq/InverseForeignKey.java new file mode 100644 index 0000000000..3c15a037e1 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/InverseForeignKey.java @@ -0,0 +1,90 @@ +/* + * 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; + +import java.util.Collection; +import java.util.List; + +import org.jooq.exception.DataAccessException; + +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An InverseForeignKey is an inverse {@link ForeignKey}. It + * represents an inverse FOREIGN KEY relationship between two + * tables. + *

+ * Instances of this type cannot be created directly. They are available from + * generated code. + * + * @param The referenced KEY's owner table record + * @param The FOREIGN KEY's owner table record + * @author Lukas Eder + */ +public interface InverseForeignKey extends Key { + + /** + * The referenced UniqueKey. + */ + @NotNull + ForeignKey getForeignKey(); + + /** + * The fields that make up the referenced UniqueKey. + *

+ * This returns the order in which the fields of {@link #getKey()} are + * referenced, which is usually the same as the fields of + * {@link UniqueKey#getFields()}, but not necessarily so. + */ + @NotNull + List> getForeignKeyFields(); + + /** + * The fields that make up the referenced UniqueKey. + *

+ * This returns the order in which the fields of {@link #getKey()} are + * referenced, which is usually the same as the fields of + * {@link UniqueKey#getFieldsArray()}, but not necessarily so. + * + * @see #getKeyFields() + */ + @NotNull + TableField @NotNull [] getForeignKeyFieldsArray(); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Internal.java b/jOOQ/src/main/java/org/jooq/impl/Internal.java index 815a8b19e8..b82d501265 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Internal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Internal.java @@ -62,6 +62,7 @@ import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.Identity; import org.jooq.Index; +import org.jooq.InverseForeignKey; import org.jooq.Name; import org.jooq.OrderField; import org.jooq.ParamMode; @@ -306,6 +307,25 @@ public final class Internal { return DSL.name("alias_" + hash(name)); } + /** + * Factory method for path aliases. + */ + @NotNull + public static final Name createPathAlias(Table parent, InverseForeignKey path) { + Name name = DSL.name(path.getName()); + + if (parent instanceof TableImpl t) { + Table ancestor = t.parent; + + if (ancestor != null) + name = createPathAlias(ancestor, t.parentPath).append(name); + else + name = parent.getQualifiedName().append(name); + } + + return DSL.name("alias_" + hash(name)); + } + /** * Factory method for parameters. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/Interpreter.java b/jOOQ/src/main/java/org/jooq/impl/Interpreter.java index 4abef4c23b..2cf626a1aa 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Interpreter.java +++ b/jOOQ/src/main/java/org/jooq/impl/Interpreter.java @@ -1733,7 +1733,7 @@ final class Interpreter { private final class InterpretedTable extends TableImpl { InterpretedTable(MutableSchema.InterpretedSchema schema) { - super(MutableTable.this.name(), schema, null, null, null, null, MutableTable.this.comment(), MutableTable.this.options); + super(MutableTable.this.name(), schema, null, (ForeignKey) null, null, null, MutableTable.this.comment(), MutableTable.this.options); for (MutableField field : MutableTable.this.fields) createField(field.name(), field.type, field.comment() != null ? field.comment().getComment() : null); diff --git a/jOOQ/src/main/java/org/jooq/impl/InverseReferenceImpl.java b/jOOQ/src/main/java/org/jooq/impl/InverseReferenceImpl.java new file mode 100644 index 0000000000..c469257c44 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/InverseReferenceImpl.java @@ -0,0 +1,89 @@ +/* + * 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.impl; + +import java.util.List; + +import org.jooq.ConstraintEnforcementStep; +import org.jooq.Record; +import org.jooq.ForeignKey; +import org.jooq.InverseForeignKey; +import org.jooq.TableField; +import org.jooq.impl.QOM.UTransient; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Lukas Eder + */ +class InverseReferenceImpl +extends + AbstractKey +implements + InverseForeignKey, + UTransient +{ + + private final ForeignKey foreignKey; + + InverseReferenceImpl(ForeignKey foreignKey) { + super(foreignKey.getKey().getTable(), foreignKey.getQualifiedName(), foreignKey.getKeyFieldsArray(), foreignKey.enforced()); + + this.foreignKey = foreignKey; + } + + @Override + public final ForeignKey getForeignKey() { + return foreignKey; + } + + @Override + public final List> getForeignKeyFields() { + return foreignKey.getFields(); + } + + @Override + public final TableField[] getForeignKeyFieldsArray() { + return foreignKey.getFieldsArray(); + } + + @Override + ConstraintEnforcementStep constraint0() { + return ((ReferenceImpl) foreignKey).constraint0(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java b/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java index 24d9fbe125..54dd18bd24 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java @@ -733,7 +733,7 @@ final class MetaImpl extends AbstractMeta { private final Result uks; MetaTable(String name, MetaSchema schema, Result columns, Result uks, String remarks, TableType tableType) { - super(name(name), schema, null, null, null, null, comment(remarks), tableOption(dsl(), schema, name, tableType)); + super(name(name), schema, null, (ForeignKey) null, null, null, comment(remarks), tableOption(dsl(), schema, name, tableType)); // Possible scenarios for columns being null: // - The "table" is in fact a SYNONYM diff --git a/jOOQ/src/main/java/org/jooq/impl/ReferenceImpl.java b/jOOQ/src/main/java/org/jooq/impl/ReferenceImpl.java index b7fb75b7dc..26e23c54b2 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ReferenceImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ReferenceImpl.java @@ -51,6 +51,7 @@ import org.jooq.ConstraintEnforcementStep; import org.jooq.DSLContext; import org.jooq.Field; import org.jooq.ForeignKey; +import org.jooq.InverseForeignKey; import org.jooq.Name; import org.jooq.Record; import org.jooq.Result; @@ -64,12 +65,18 @@ import org.jooq.impl.QOM.UEmpty; /** * @author Lukas Eder */ -final class ReferenceImpl extends AbstractKey implements ForeignKey, UEmpty { +final class ReferenceImpl +extends + AbstractKey +implements + ForeignKey, + UEmpty +{ - private final UniqueKey uk; - private final TableField[] ukFields; + private final UniqueKey uk; + private final TableField[] ukFields; - ReferenceImpl(Table table, Name name, TableField[] fkFields, UniqueKey uk, TableField[] ukFields, boolean enforced) { + ReferenceImpl(Table table, Name name, TableField[] fkFields, UniqueKey uk, TableField[] ukFields, boolean enforced) { super(table, name, fkFields, enforced); this.uk = uk; @@ -77,33 +84,38 @@ final class ReferenceImpl extends AbstractKe } @Override - public final UniqueKey getKey() { + public final InverseForeignKey getInverseKey() { + return new InverseReferenceImpl<>(this); + } + + @Override + public final UniqueKey getKey() { return uk; } @Override - public final List> getKeyFields() { + public final List> getKeyFields() { return Arrays.asList(ukFields); } @Override - public final TableField[] getKeyFieldsArray() { + public final TableField[] getKeyFieldsArray() { return ukFields; } @Override - public final O fetchParent(R record) { + public final PARENT fetchParent(CHILD record) { return filterOne(fetchParents(record)); } @Override @SafeVarargs - public final Result fetchParents(R... records) { + public final Result fetchParents(CHILD... records) { return fetchParents(list(records)); } @Override - public final Result fetchParents(Collection records) { + public final Result fetchParents(Collection records) { if (records == null || records.size() == 0) return new ResultImpl<>(new DefaultConfiguration(), uk.getFields()); else @@ -111,18 +123,18 @@ final class ReferenceImpl extends AbstractKe } @Override - public final Result fetchChildren(O record) { + public final Result fetchChildren(PARENT record) { return fetchChildren(list(record)); } @Override @SafeVarargs - public final Result fetchChildren(O... records) { + public final Result fetchChildren(PARENT... records) { return fetchChildren(list(records)); } @Override - public final Result fetchChildren(Collection records) { + public final Result fetchChildren(Collection records) { if (records == null || records.size() == 0) return new ResultImpl<>(new DefaultConfiguration(), getFields()); else @@ -130,34 +142,34 @@ final class ReferenceImpl extends AbstractKe } @Override - public final Table parent(R record) { + public final Table parent(CHILD record) { return parents(list(record)); } @SafeVarargs @Override - public final Table parents(R... records) { + public final Table parents(CHILD... records) { return parents(list(records)); } @Override - public final Table parents(Collection records) { + public final Table parents(Collection records) { return table(records, uk.getTable(), uk.getFieldsArray(), getFieldsArray()); } @Override - public final Table children(O record) { + public final Table children(PARENT record) { return children(list(record)); } @SafeVarargs @Override - public final Table children(O... records) { + public final Table children(PARENT... records) { return children(list(records)); } @Override - public final Table children(Collection records) { + public final Table children(Collection records) { return table(records, getTable(), getFieldsArray(), uk.getFieldsArray()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/TableImpl.java b/jOOQ/src/main/java/org/jooq/impl/TableImpl.java index 86d905386f..cda3be4f36 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableImpl.java @@ -71,6 +71,7 @@ import org.jooq.Context; import org.jooq.DataType; import org.jooq.Field; import org.jooq.ForeignKey; +import org.jooq.InverseForeignKey; import org.jooq.JoinType; import org.jooq.Name; // ... @@ -124,6 +125,8 @@ implements protected final Field[] parameters; final Table child; final ForeignKey childPath; + final Table parent; + final InverseForeignKey parentPath; /** * @deprecated - 3.10 - [#5996] - Use {@link #TableImpl(Name)} instead (or @@ -171,19 +174,19 @@ implements } public TableImpl(Name name) { - this(name, null, null, null, null, null, (Comment) null); + this(name, null, null, (ForeignKey) null, null, null, (Comment) null); } public TableImpl(Name name, Schema schema) { - this(name, schema, null, null, null, null, (Comment) null); + this(name, schema, null, (ForeignKey) null, null, null, (Comment) null); } public TableImpl(Name name, Schema schema, Table aliased) { - this(name, schema, null, null, aliased, null, (Comment) null); + this(name, schema, null, (ForeignKey) null, aliased, null, (Comment) null); } public TableImpl(Name name, Schema schema, Table aliased, Field[] parameters) { - this(name, schema, null, null, aliased, parameters, (Comment) null); + this(name, schema, null, (ForeignKey) null, aliased, parameters, (Comment) null); } /** @@ -191,42 +194,79 @@ implements */ @Deprecated public TableImpl(Name name, Schema schema, Table aliased, Field[] parameters, String comment) { - this(name, schema, null, null, aliased, parameters, DSL.comment(comment)); + this(name, schema, null, (ForeignKey) null, aliased, parameters, DSL.comment(comment)); } public TableImpl(Name name, Schema schema, Table aliased, Field[] parameters, Comment comment) { - this(name, schema, null, null, aliased, parameters, comment); + this(name, schema, null, (ForeignKey) null, aliased, parameters, comment); } public TableImpl(Name name, Schema schema, Table aliased, Field[] parameters, Comment comment, TableOptions options) { - this(name, schema, null, null, aliased, parameters, comment, options); + this(name, schema, null, (ForeignKey) null, aliased, parameters, comment, options); } public TableImpl(Table child, ForeignKey path, Table parent) { this(createPathAlias(child, path), null, child, path, parent, null, parent.getCommentPart()); } + public TableImpl(Table parent, InverseForeignKey path, Table child) { + this(createPathAlias(parent, path), null, parent, path, child, null, child.getCommentPart()); + } + public TableImpl(Name name, Schema schema, Table child, ForeignKey path, Table aliased, Field[] parameters, Comment comment) { this(name, schema, child, path, aliased, parameters, comment, TableOptions.table()); } - @SuppressWarnings({ "rawtypes", "unchecked" }) + public TableImpl(Name name, Schema schema, Table parent, InverseForeignKey path, Table aliased, Field[] parameters, Comment comment) { + this(name, schema, parent, path, aliased, parameters, comment, TableOptions.table()); + } + public TableImpl(Name name, Schema schema, Table child, ForeignKey path, Table aliased, Field[] parameters, Comment comment, TableOptions options) { + this(name, schema, child, path, null, null, aliased, parameters, comment, options); + } + + public TableImpl(Name name, Schema schema, Table parent, InverseForeignKey path, Table aliased, Field[] parameters, Comment comment, TableOptions options) { + this(name, schema, null, null, parent, path, aliased, parameters, comment, options); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private TableImpl( + Name name, + Schema schema, + Table child, ForeignKey childPath, + Table parent, InverseForeignKey parentPath, + Table aliased, + Field[] parameters, + Comment comment, + TableOptions options + ) { super(options, name, schema, comment); this.fields = new FieldsImpl<>(); if (child != null) { this.child = child; - this.childPath = path == null ? null : Tools.aliasedKey((ForeignKey) path, child, this); + this.childPath = childPath == null ? null : Tools.aliasedKey((ForeignKey) childPath, child, this); + this.parent = null; + this.parentPath = null; + } + else if (parent != null) { + this.child = null; + this.childPath = null; + this.parent = parent; + this.parentPath = parentPath == null ? null : Tools.aliasedKey((ForeignKey) parentPath.getForeignKey(), this, parent).getInverseKey(); } else if (aliased instanceof TableImpl t) { this.child = t.child; this.childPath = t.childPath; + this.parent = t.parent; + this.parentPath = t.parentPath; } else { this.child = null; this.childPath = null; + this.parent = null; + this.parentPath = null; } if (aliased != null) {