[jOOQ/jOOQ#13639] Add support for to-many path expressions

- Added code generation support
- Added runtime meta data
- Mark the feature as experimental
- Turn off the feature by default, for as long it's experimental
This commit is contained in:
Lukas Eder 2023-04-26 15:18:09 +02:00
parent 55a9488101
commit 49b48887c7
21 changed files with 697 additions and 73 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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)

View File

@ -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.

View File

@ -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<out %s>, key: %s<out %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 {
out.println("%s<O extends %s> %s(%s<O> child, %s<O, %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<out %s>, key: %s<out %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 {
out.println("%s<O extends %s> %s(%s<O> child, %s<O, %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<out %s>, key: %s<out %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 {
out.println("%s<O extends %s> %s(%s<O> parent, %s<O, %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<ForeignKeyDefinition> foreignKeys = table.getForeignKeys();
List<ForeignKeyDefinition> 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<String> 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<TableDefinition, Long> pathCounts = foreignKeys.stream().collect(groupingBy(ForeignKeyDefinition::getReferencedTable, counting()));
for (ForeignKeyDefinition foreignKey : foreignKeys) {
Map<TableDefinition, Long> 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<InverseForeignKeyDefinition> 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<TableDefinition, Long> 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 <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) {
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<CheckConstraintDefinition> cc = table.getCheckConstraints();

View File

@ -160,6 +160,28 @@ implements
return result;
}
@Override
public final List<InverseForeignKeyDefinition> getInverseForeignKeys() {
List<InverseForeignKeyDefinition> result = new ArrayList<>();
for (UniqueKeyDefinition uk : getKeys())
for (ForeignKeyDefinition fk : uk.getForeignKeys())
result.add(fk.inverse());
return result;
}
@Override
public final List<InverseForeignKeyDefinition> getInverseForeignKeys(TableDefinition referencing) {
List<InverseForeignKeyDefinition> result = new ArrayList<>();
for (InverseForeignKeyDefinition key : getInverseForeignKeys())
if (referencing.equals(key.getReferencingTable()))
result.add(key);
return result;
}
@Override
public final List<CheckConstraintDefinition> getCheckConstraints() {
return getDatabase().getRelations().getCheckConstraints(this);

View File

@ -100,4 +100,9 @@ public class DefaultForeignKeyDefinition extends AbstractConstraintDefinition im
return keys.size();
}
@Override
public InverseForeignKeyDefinition inverse() {
return new DefaultInverseForeignKeyDefinition(this);
}
}

View File

@ -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<ColumnDefinition> getKeyColumns() {
return foreignKey.getReferencedColumns();
}
@Override
public TableDefinition getReferencingTable() {
return foreignKey.getTable();
}
@Override
public List<ColumnDefinition> getReferencingColumns() {
return foreignKey.getKeyColumns();
}
@Override
public ForeignKeyDefinition foreignKey() {
return foreignKey;
}
}

View File

@ -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();
}

View File

@ -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<ColumnDefinition> getKeyColumns();
/**
* The definition of the referencing table.
*/
TableDefinition getReferencingTable();
/**
* The list of columns referencing this unique key from the foreign key.
*/
List<ColumnDefinition> getReferencingColumns();
/**
* Get the foreign key this is an inverse of.
*/
ForeignKeyDefinition foreignKey();
}

View File

@ -128,6 +128,16 @@ public interface TableDefinition extends Definition {
*/
List<ForeignKeyDefinition> getForeignKeys(TableDefinition referenced);
/**
* Get the inverse foreign keys for this table.
*/
List<InverseForeignKeyDefinition> getInverseForeignKeys();
/**
* Get the inverse foreign keys for this table referenced from a specific table.
*/
List<InverseForeignKeyDefinition> getInverseForeignKeys(TableDefinition referencing);
/**
* Get the <code>CHECK</code> constraints for this table.
*/

View File

@ -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)<p><strong>EXPERIMENTAL functionality! Do not use this feature, yet</strong>
*
* @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()));

View File

@ -1951,6 +1951,10 @@ This is a prerequisite for various advanced features]]></jxb:javadoc></jxb:prope
<element name="implicitJoinPathsToOne" type="boolean" default="true" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Generate implicit join path constructors on generated tables for outgoing foreign key relationships (to-one relationships)]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="implicitJoinPathsToMany" type="boolean" default="false" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Generate implicit join path constructors on generated tables for incoming foreign key relationships (to-many relationships)<p><strong>EXPERIMENTAL functionality! Do not use this feature, yet</strong>]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="implicitJoinPathsUseTableNameForUnambiguousFKs" type="boolean" default="true" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether names of unambiguous {@link org.jooq.meta.ForeignKeyDefinition} should be based

View File

@ -53,18 +53,24 @@ import org.jetbrains.annotations.Nullable;
* Instances of this type cannot be created directly. They are available from
* generated code.
*
* @param <R> The <code>FOREIGN KEY</code>'s owner table record
* @param <O> The referenced <code>KEY</code>'s owner table record
* @param <CHILD> The <code>FOREIGN KEY</code>'s owner table record
* @param <PARENT> The referenced <code>KEY</code>'s owner table record
* @author Lukas Eder
*/
@SuppressWarnings("unchecked")
public interface ForeignKey<R extends Record, O extends Record> extends Key<R> {
public interface ForeignKey<CHILD extends Record, PARENT extends Record> extends Key<CHILD> {
/**
* The inverse key.
*/
@NotNull
InverseForeignKey<PARENT, CHILD> getInverseKey();
/**
* The referenced <code>UniqueKey</code>.
*/
@NotNull
UniqueKey<O> getKey();
UniqueKey<PARENT> getKey();
/**
* The fields that make up the referenced <code>UniqueKey</code>.
@ -74,7 +80,7 @@ public interface ForeignKey<R extends Record, O extends Record> extends Key<R> {
* {@link UniqueKey#getFields()}, but not necessarily so.
*/
@NotNull
List<TableField<O, ?>> getKeyFields();
List<TableField<PARENT, ?>> getKeyFields();
/**
* The fields that make up the referenced <code>UniqueKey</code>.
@ -86,7 +92,7 @@ public interface ForeignKey<R extends Record, O extends Record> extends Key<R> {
* @see #getKeyFields()
*/
@NotNull
TableField<O, ?> @NotNull [] getKeyFieldsArray();
TableField<PARENT, ?> @NotNull [] getKeyFieldsArray();
/**
* Fetch a parent record of a given record through this foreign key
@ -100,7 +106,7 @@ public interface ForeignKey<R extends Record, O extends Record> extends Key<R> {
*/
@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<R extends Record, O extends Record> extends Key<R> {
*/
@NotNull
@Blocking
Result<O> fetchParents(R... records) throws DataAccessException;
Result<PARENT> 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<R extends Record, O extends Record> extends Key<R> {
*/
@NotNull
@Blocking
Result<O> fetchParents(Collection<? extends R> records) throws DataAccessException;
Result<PARENT> fetchParents(Collection<? extends CHILD> records) throws DataAccessException;
/**
* Fetch child records of a given record through this foreign key
@ -142,7 +148,7 @@ public interface ForeignKey<R extends Record, O extends Record> extends Key<R> {
*/
@NotNull
@Blocking
Result<R> fetchChildren(O record) throws DataAccessException;
Result<CHILD> 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<R extends Record, O extends Record> extends Key<R> {
*/
@NotNull
@Blocking
Result<R> fetchChildren(O... records) throws DataAccessException;
Result<CHILD> 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<R extends Record, O extends Record> extends Key<R> {
*/
@NotNull
@Blocking
Result<R> fetchChildren(Collection<? extends O> records) throws DataAccessException;
Result<CHILD> fetchChildren(Collection<? extends PARENT> records) throws DataAccessException;
/**
* Get a table expression representing the parent of a record, given this
* foreign key.
*/
@NotNull
Table<O> parent(R record);
Table<PARENT> parent(CHILD record);
/**
* Get a table expression representing the parents of a record, given this
* foreign key.
*/
@NotNull
Table<O> parents(R... records);
Table<PARENT> parents(CHILD... records);
/**
* Get a table expression representing the parents of a record, given this
* foreign key.
*/
@NotNull
Table<O> parents(Collection<? extends R> records);
Table<PARENT> parents(Collection<? extends CHILD> records);
/**
* Get a table expression representing the children of a record, given this
* foreign key.
*/
@NotNull
Table<R> children(O record);
Table<CHILD> children(PARENT record);
/**
* Get a table expression representing the children of a record, given this
* foreign key.
*/
@NotNull
Table<R> children(O... records);
Table<CHILD> children(PARENT... records);
/**
* Get a table expression representing the children of a record, given this
* foreign key.
*/
@NotNull
Table<R> children(Collection<? extends O> records);
Table<CHILD> children(Collection<? extends PARENT> records);
}

View File

@ -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 <code>InverseForeignKey</code> is an inverse {@link ForeignKey}. It
* represents an inverse <code>FOREIGN KEY</code> relationship between two
* tables.
* <p>
* Instances of this type cannot be created directly. They are available from
* generated code.
*
* @param <PARENT> The referenced <code>KEY</code>'s owner table record
* @param <CHILD> The <code>FOREIGN KEY</code>'s owner table record
* @author Lukas Eder
*/
public interface InverseForeignKey<PARENT extends Record, CHILD extends Record> extends Key<PARENT> {
/**
* The referenced <code>UniqueKey</code>.
*/
@NotNull
ForeignKey<CHILD, PARENT> getForeignKey();
/**
* The fields that make up the referenced <code>UniqueKey</code>.
* <p>
* 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<TableField<CHILD, ?>> getForeignKeyFields();
/**
* The fields that make up the referenced <code>UniqueKey</code>.
* <p>
* 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<CHILD, ?> @NotNull [] getForeignKeyFieldsArray();
}

View File

@ -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.
*/

View File

@ -1733,7 +1733,7 @@ final class Interpreter {
private final class InterpretedTable extends TableImpl<Record> {
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<?, Record>) 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);

View File

@ -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<R extends Record, O extends Record>
extends
AbstractKey<R>
implements
InverseForeignKey<R, O>,
UTransient
{
private final ForeignKey<O, R> foreignKey;
InverseReferenceImpl(ForeignKey<O, R> foreignKey) {
super(foreignKey.getKey().getTable(), foreignKey.getQualifiedName(), foreignKey.getKeyFieldsArray(), foreignKey.enforced());
this.foreignKey = foreignKey;
}
@Override
public final ForeignKey<O, R> getForeignKey() {
return foreignKey;
}
@Override
public final List<TableField<O, ?>> getForeignKeyFields() {
return foreignKey.getFields();
}
@Override
public final TableField<O, ?>[] getForeignKeyFieldsArray() {
return foreignKey.getFieldsArray();
}
@Override
ConstraintEnforcementStep constraint0() {
return ((ReferenceImpl<O, R>) foreignKey).constraint0();
}
}

View File

@ -733,7 +733,7 @@ final class MetaImpl extends AbstractMeta {
private final Result<Record> uks;
MetaTable(String name, MetaSchema schema, Result<Record> columns, Result<Record> 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<?, Record>) null, null, null, comment(remarks), tableOption(dsl(), schema, name, tableType));
// Possible scenarios for columns being null:
// - The "table" is in fact a SYNONYM

View File

@ -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<R extends Record, O extends Record> extends AbstractKey<R> implements ForeignKey<R, O>, UEmpty {
final class ReferenceImpl<CHILD extends Record, PARENT extends Record>
extends
AbstractKey<CHILD>
implements
ForeignKey<CHILD, PARENT>,
UEmpty
{
private final UniqueKey<O> uk;
private final TableField<O, ?>[] ukFields;
private final UniqueKey<PARENT> uk;
private final TableField<PARENT, ?>[] ukFields;
ReferenceImpl(Table<R> table, Name name, TableField<R, ?>[] fkFields, UniqueKey<O> uk, TableField<O, ?>[] ukFields, boolean enforced) {
ReferenceImpl(Table<CHILD> table, Name name, TableField<CHILD, ?>[] fkFields, UniqueKey<PARENT> uk, TableField<PARENT, ?>[] ukFields, boolean enforced) {
super(table, name, fkFields, enforced);
this.uk = uk;
@ -77,33 +84,38 @@ final class ReferenceImpl<R extends Record, O extends Record> extends AbstractKe
}
@Override
public final UniqueKey<O> getKey() {
public final InverseForeignKey<PARENT, CHILD> getInverseKey() {
return new InverseReferenceImpl<>(this);
}
@Override
public final UniqueKey<PARENT> getKey() {
return uk;
}
@Override
public final List<TableField<O, ?>> getKeyFields() {
public final List<TableField<PARENT, ?>> getKeyFields() {
return Arrays.asList(ukFields);
}
@Override
public final TableField<O, ?>[] getKeyFieldsArray() {
public final TableField<PARENT, ?>[] 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<O> fetchParents(R... records) {
public final Result<PARENT> fetchParents(CHILD... records) {
return fetchParents(list(records));
}
@Override
public final Result<O> fetchParents(Collection<? extends R> records) {
public final Result<PARENT> fetchParents(Collection<? extends CHILD> records) {
if (records == null || records.size() == 0)
return new ResultImpl<>(new DefaultConfiguration(), uk.getFields());
else
@ -111,18 +123,18 @@ final class ReferenceImpl<R extends Record, O extends Record> extends AbstractKe
}
@Override
public final Result<R> fetchChildren(O record) {
public final Result<CHILD> fetchChildren(PARENT record) {
return fetchChildren(list(record));
}
@Override
@SafeVarargs
public final Result<R> fetchChildren(O... records) {
public final Result<CHILD> fetchChildren(PARENT... records) {
return fetchChildren(list(records));
}
@Override
public final Result<R> fetchChildren(Collection<? extends O> records) {
public final Result<CHILD> fetchChildren(Collection<? extends PARENT> records) {
if (records == null || records.size() == 0)
return new ResultImpl<>(new DefaultConfiguration(), getFields());
else
@ -130,34 +142,34 @@ final class ReferenceImpl<R extends Record, O extends Record> extends AbstractKe
}
@Override
public final Table<O> parent(R record) {
public final Table<PARENT> parent(CHILD record) {
return parents(list(record));
}
@SafeVarargs
@Override
public final Table<O> parents(R... records) {
public final Table<PARENT> parents(CHILD... records) {
return parents(list(records));
}
@Override
public final Table<O> parents(Collection<? extends R> records) {
public final Table<PARENT> parents(Collection<? extends CHILD> records) {
return table(records, uk.getTable(), uk.getFieldsArray(), getFieldsArray());
}
@Override
public final Table<R> children(O record) {
public final Table<CHILD> children(PARENT record) {
return children(list(record));
}
@SafeVarargs
@Override
public final Table<R> children(O... records) {
public final Table<CHILD> children(PARENT... records) {
return children(list(records));
}
@Override
public final Table<R> children(Collection<? extends O> records) {
public final Table<CHILD> children(Collection<? extends PARENT> records) {
return table(records, getTable(), getFieldsArray(), uk.getFieldsArray());
}

View File

@ -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<?, R> childPath;
final Table<?> parent;
final InverseForeignKey<?, R> 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<?, R>) 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<?, R>) null, null, null, (Comment) null);
}
public TableImpl(Name name, Schema schema, Table<R> aliased) {
this(name, schema, null, null, aliased, null, (Comment) null);
this(name, schema, null, (ForeignKey<?, R>) null, aliased, null, (Comment) null);
}
public TableImpl(Name name, Schema schema, Table<R> aliased, Field<?>[] parameters) {
this(name, schema, null, null, aliased, parameters, (Comment) null);
this(name, schema, null, (ForeignKey<?, R>) null, aliased, parameters, (Comment) null);
}
/**
@ -191,42 +194,79 @@ implements
*/
@Deprecated
public TableImpl(Name name, Schema schema, Table<R> aliased, Field<?>[] parameters, String comment) {
this(name, schema, null, null, aliased, parameters, DSL.comment(comment));
this(name, schema, null, (ForeignKey<?, R>) null, aliased, parameters, DSL.comment(comment));
}
public TableImpl(Name name, Schema schema, Table<R> aliased, Field<?>[] parameters, Comment comment) {
this(name, schema, null, null, aliased, parameters, comment);
this(name, schema, null, (ForeignKey<?, R>) null, aliased, parameters, comment);
}
public TableImpl(Name name, Schema schema, Table<R> aliased, Field<?>[] parameters, Comment comment, TableOptions options) {
this(name, schema, null, null, aliased, parameters, comment, options);
this(name, schema, null, (ForeignKey<?, R>) null, aliased, parameters, comment, options);
}
public TableImpl(Table<?> child, ForeignKey<?, R> path, Table<R> parent) {
this(createPathAlias(child, path), null, child, path, parent, null, parent.getCommentPart());
}
public TableImpl(Table<?> parent, InverseForeignKey<?, R> path, Table<R> child) {
this(createPathAlias(parent, path), null, parent, path, child, null, child.getCommentPart());
}
public TableImpl(Name name, Schema schema, Table<?> child, ForeignKey<?, R> path, Table<R> 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<?, R> path, Table<R> aliased, Field<?>[] parameters, Comment comment) {
this(name, schema, parent, path, aliased, parameters, comment, TableOptions.table());
}
public TableImpl(Name name, Schema schema, Table<?> child, ForeignKey<?, R> path, Table<R> 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<?, R> path, Table<R> 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<?, R> childPath,
Table<?> parent, InverseForeignKey<?, R> parentPath,
Table<R> 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) {