Skip to content

Commit

Permalink
sebersole#97 - Bind JOINED inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed Nov 21, 2023
1 parent 407766c commit d4cefaf
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -52,6 +53,7 @@
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.PrimaryKey;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SingleTableSubclass;
import org.hibernate.mapping.Subclass;
Expand All @@ -68,7 +70,10 @@
import jakarta.persistence.DiscriminatorType;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.SharedCacheMode;

import static org.hibernate.boot.models.bind.ModelBindingLogging.MODEL_BINDING_LOGGER;
Expand Down Expand Up @@ -139,7 +144,6 @@ public EntityTypeBinder(
final IdentifiableTypeBinder superTypeBinder = getSuperTypeBinder();
final EntityTypeBinder superEntityBinder = getSuperEntityBinder();
if ( binding instanceof RootClass rootClass ) {

assert superEntityBinder == null;

if ( superTypeBinder != null ) {
Expand Down Expand Up @@ -385,8 +389,8 @@ private UnionSubclass createUnionSubclass(IdentifiableTypeClass superTypeBinding
else {
assert superTypeBinding instanceof MappedSuperclass;

final PersistentClass superEntity = resolveSuperEntity( superTypeBinding );
final UnionSubclass binding = new UnionSubclass(
final var superEntity = resolveSuperEntity( superTypeBinding );
final var binding = new UnionSubclass(
superEntity,
getBindingState().getMetadataBuildingContext()
);
Expand All @@ -396,20 +400,114 @@ private UnionSubclass createUnionSubclass(IdentifiableTypeClass superTypeBinding
}

private JoinedSubclass createJoinedSubclass(IdentifiableTypeClass superTypeBinding) {
final JoinedSubclass joinedSubclass;

final var superEntityTypeBinding = getSuperEntityBinder().getTypeBinding();
if ( superTypeBinding instanceof PersistentClass superEntity ) {
return new JoinedSubclass( superEntity, getBindingState().getMetadataBuildingContext() );
joinedSubclass = new JoinedSubclass(
superEntity,
getBindingState().getMetadataBuildingContext()
);
}
else {
assert superTypeBinding instanceof MappedSuperclass;

final PersistentClass superEntity = resolveSuperEntity( superTypeBinding );
final JoinedSubclass binding = new JoinedSubclass(
superEntity,
joinedSubclass = new JoinedSubclass(
superEntityTypeBinding,
getBindingState().getMetadataBuildingContext()
);
binding.setSuperMappedSuperclass( (MappedSuperclass) superTypeBinding );
return binding;
joinedSubclass.setSuperMappedSuperclass( (MappedSuperclass) superTypeBinding );
}

final var joinTableReference = modelBinders.getTableBinder().bindPrimaryTable(
getManagedType(),
EntityHierarchy.HierarchyRelation.SUB
);
joinedSubclass.setTable( joinTableReference.binding() );

final PrimaryKey primaryKey = new PrimaryKey( joinTableReference.binding() );
joinTableReference.binding().setPrimaryKey( primaryKey );

final var targetTable = superEntityTypeBinding.getIdentityTable();
if ( targetTable.getPrimaryKey() != null && targetTable.getPrimaryKey().getColumnSpan() > 0 ) {
// we can create the foreign key immediately
final var joinTableAnn = getManagedType().getClassDetails().getAnnotationUsage( JoinTable.class );

final List<AnnotationUsage<JoinColumn>> joinColumnAnns = BindingHelper.getValue(
joinTableAnn,
"joinColumns",
Collections.emptyList()
);
final List<AnnotationUsage<JoinColumn>> inverseJoinColumnAnns = BindingHelper.getValue(
joinTableAnn,
"inverseJoinColumns",
Collections.emptyList()
);

for ( int i = 0; i < targetTable.getPrimaryKey().getColumnSpan(); i++ ) {
final Column targetColumn = targetTable.getPrimaryKey().getColumns().get( i );
final Column pkColumn;
if ( !inverseJoinColumnAnns.isEmpty() ) {
final var joinColumnAnn = resolveMatchingJoinColumnAnn(
inverseJoinColumnAnns,
targetColumn,
joinColumnAnns
);
pkColumn = ColumnBinder.bindColumn( joinColumnAnn, targetColumn::getName, true, false );
}
else {
pkColumn = ColumnBinder.bindColumn( null, targetColumn::getName, true, false );
}
primaryKey.addColumn( pkColumn );
}

final AnnotationUsage<ForeignKey> foreignKeyAnn = BindingHelper.getValue( joinTableAnn, "foreignKey", (AnnotationUsage<ForeignKey>) null );
final String foreignKeyName = foreignKeyAnn == null
? ""
: BindingHelper.getString( foreignKeyAnn, "name", ForeignKey.class, getBindingContext() );
final String foreignKeyDefinition = foreignKeyAnn == null
? ""
: BindingHelper.getString( foreignKeyAnn, "foreignKeyDefinition", ForeignKey.class, getBindingContext() );

final org.hibernate.mapping.ForeignKey foreignKey = targetTable.createForeignKey(
foreignKeyName,
primaryKey.getColumns(),
findSuperEntity().getEntityName(),
foreignKeyDefinition,
targetTable.getPrimaryKey().getColumns()
);
foreignKey.setReferencedTable( targetTable );
}
else {
throw new UnsupportedOperationException( "Delayed foreign key creation not yet implemented" );
}

// todo : bind foreign-key
// todo : do same for secondary tables
// - in both cases we can immediately process the fk if

return joinedSubclass;
}

private AnnotationUsage<JoinColumn> resolveMatchingJoinColumnAnn(
List<AnnotationUsage<JoinColumn>> inverseJoinColumnAnns,
Column pkColumn,
List<AnnotationUsage<JoinColumn>> joinColumnAnns) {
int matchPosition = -1;
for ( int j = 0; j < inverseJoinColumnAnns.size(); j++ ) {
final var inverseJoinColumnAnn = inverseJoinColumnAnns.get( j );
final String name = inverseJoinColumnAnn.getString( "name" );
if ( pkColumn.getName().equals( name ) ) {
matchPosition = j;
break;
}
}

if ( matchPosition == -1 ) {
throw new MappingException( "Unable to match primary key column [" + pkColumn.getName() + "] to any inverseJoinColumn - " + getManagedType().getEntityName() );
}

return joinColumnAnns.get( matchPosition );
}

private SingleTableSubclass createSingleTableSubclass(IdentifiableTypeClass superTypeBinding) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.hibernate.models.spi.ClassDetails;

import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinTable;
import jakarta.persistence.SecondaryTable;

/**
Expand Down Expand Up @@ -87,41 +88,70 @@ public TableBinder(
public TableReference bindPrimaryTable(EntityTypeMetadata type, EntityHierarchy.HierarchyRelation hierarchyRelation) {
final ClassDetails typeClassDetails = type.getClassDetails();
final AnnotationUsage<jakarta.persistence.Table> tableAnn = typeClassDetails.getAnnotationUsage( jakarta.persistence.Table.class );
final AnnotationUsage<JoinTable> joinTableAnn = typeClassDetails.getAnnotationUsage( JoinTable.class );
final AnnotationUsage<Subselect> subselectAnn = typeClassDetails.getAnnotationUsage( Subselect.class );

if ( tableAnn != null && joinTableAnn != null ) {
throw new AnnotationPlacementException( "Illegal combination of @Table and @JoinTable on " + typeClassDetails.getName() );
}
if ( tableAnn != null && subselectAnn != null ) {
throw new AnnotationPlacementException( "Illegal combination of @Table and @Subselect on " + typeClassDetails.getName() );
}
if ( joinTableAnn != null && subselectAnn != null ) {
throw new AnnotationPlacementException( "Illegal combination of @JoinTable and @Subselect on " + typeClassDetails.getName() );
}

final TableReference tableReference;

if ( type.getHierarchy().getInheritanceType() == InheritanceType.TABLE_PER_CLASS ) {
assert subselectAnn == null;

if ( hierarchyRelation == EntityHierarchy.HierarchyRelation.ROOT ) {
tableReference = bindPhysicalTable( type, tableAnn, true );
tableReference = bindPhysicalTable( type, tableAnn, jakarta.persistence.Table.class, true );
}
else {
tableReference = bindUnionTable( type, tableAnn );
}
}
else {
if ( subselectAnn != null ) {
if ( tableAnn != null ) {
throw new AnnotationPlacementException( "Illegal combination of @Table and @Subselect on " + typeClassDetails.getName() );
}
tableReference = bindVirtualTable( type, subselectAnn );
else if ( type.getHierarchy().getInheritanceType() == InheritanceType.SINGLE_TABLE ) {
if ( hierarchyRelation == EntityHierarchy.HierarchyRelation.ROOT ) {
tableReference = normalTableDetermination( type, subselectAnn, tableAnn, jakarta.persistence.Table.class, typeClassDetails );
}
else {
// either an explicit or implicit @Table
tableReference = bindPhysicalTable( type, tableAnn, true );
tableReference = null;
}
}
else {
tableReference = normalTableDetermination( type, subselectAnn, joinTableAnn, JoinTable.class, typeClassDetails );
}

bindingState.addTable( type, tableReference );
if ( tableReference != null ) {
bindingState.addTable( type, tableReference );

final PrimaryKey primaryKey = new PrimaryKey( tableReference.binding() );
tableReference.binding().setPrimaryKey( primaryKey );
final PrimaryKey primaryKey = new PrimaryKey( tableReference.binding() );
tableReference.binding().setPrimaryKey( primaryKey );
}

return tableReference;
}

private <A extends Annotation> TableReference normalTableDetermination(
EntityTypeMetadata type,
AnnotationUsage<Subselect> subselectAnn,
AnnotationUsage<A> tableAnn,
Class<A> annotationType,
ClassDetails typeClassDetails) {
final TableReference tableReference;
if ( subselectAnn != null ) {
tableReference = bindVirtualTable( type, subselectAnn );
}
else {
// either an explicit or implicit @Table
tableReference = bindPhysicalTable( type, tableAnn, annotationType, true );
}
return tableReference;
}

private TableReference bindUnionTable(
EntityTypeMetadata type,
AnnotationUsage<jakarta.persistence.Table> tableAnn) {
Expand Down Expand Up @@ -206,12 +236,13 @@ public MetadataBuildingContext getBuildingContext() {
);
}

private PhysicalTableReference bindPhysicalTable(
private <A extends Annotation> PhysicalTableReference bindPhysicalTable(
EntityTypeMetadata type,
AnnotationUsage<jakarta.persistence.Table> tableAnn,
AnnotationUsage<A> tableAnn,
Class<A> annotationType,
boolean isPrimary) {
if ( tableAnn != null ) {
return bindExplicitPhysicalTable( type, tableAnn, isPrimary );
return bindExplicitPhysicalTable( type, tableAnn, annotationType, isPrimary );
}
else {
return bindImplicitPhysicalTable( type, isPrimary );
Expand Down Expand Up @@ -292,22 +323,23 @@ public MetadataBuildingContext getBuildingContext() {
);
}

private PhysicalTable bindExplicitPhysicalTable(
private <A extends Annotation> PhysicalTable bindExplicitPhysicalTable(
EntityTypeMetadata type,
AnnotationUsage<jakarta.persistence.Table> tableAnn,
AnnotationUsage<A> tableAnn,
Class<A> annotationType,
boolean isPrimary) {
final Identifier logicalName = determineLogicalName( type, tableAnn );
final Identifier logicalSchemaName = resolveDatabaseIdentifier(
tableAnn,
"schema",
jakarta.persistence.Table.class,
annotationType,
bindingOptions.getDefaultSchemaName(),
QuotedIdentifierTarget.SCHEMA_NAME
);
final Identifier logicalCatalogName = resolveDatabaseIdentifier(
tableAnn,
"catalog",
jakarta.persistence.Table.class,
annotationType,
bindingOptions.getDefaultCatalogName(),
QuotedIdentifierTarget.CATALOG_NAME
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static void bindVersion(
final Property property = new Property();
property.setName( attributeMetadata.getName() );
typeBinding.setVersion( property );
typeBinding.addProperty( property );

final BasicValue basicValue = new BasicValue(
bindingState.getMetadataBuildingContext(),
Expand Down
Loading

0 comments on commit d4cefaf

Please sign in to comment.