HHH-7124 : Bind many-to-one foreign key

This commit is contained in:
Gail Badner 2012-03-20 13:13:16 -07:00
parent ebbaf80b5f
commit 35081296d3
6 changed files with 163 additions and 53 deletions

View File

@ -353,6 +353,7 @@ private void bindCollectionKey(
StringHelper.isEmpty( keySource.getExplicitForeignKeyName() )
? null
: quotedIdentifier( keySource.getExplicitForeignKeyName() );
// TODO: deal with secondary tables...
final TableSpecification table = attributeBinding.getContainer().seekEntityBinding().getPrimaryTable();
keyBinding.prepareForeignKey( foreignKeyName, collectionTable, table );
keyBinding.getForeignKey().setDeleteRule( keySource.getOnDeleteAction() );
@ -506,6 +507,110 @@ private CompositeAttributeBinding bindComponentAttribute(
return attributeBinding;
}
private ForeignKey createOrLocateForeignKey(
String foreignKeyName,
SingularAttributeBinding sourceAttributeBinding,
SingularAttributeBinding targetAttributeBinding) {
if ( sourceAttributeBinding.getRelationalValueBindings().isEmpty() ) {
throw new MappingException(
String.format(
"Cannot create foreign key for attribute (%s) because it has no columns/derived values configured.",
sourceAttributeBinding.getAttribute().getName()
),
bindingContexts.peek().getOrigin()
);
}
if ( targetAttributeBinding.getRelationalValueBindings().isEmpty() ) {
throw new MappingException(
String.format(
"Cannot create foreign key for attribute (%s) because the target attribute (%s) has no columns/derived values configured.",
sourceAttributeBinding.getAttribute().getName(),
targetAttributeBinding.getAttribute().getName()
),
bindingContexts.peek().getOrigin()
);
}
// TODO: deal with secondary tables
// for now just use the the EntityBinding primary table.
return createOrLocateForeignKey(
foreignKeyName,
sourceAttributeBinding.getContainer().seekEntityBinding().getPrimaryTable(),
targetAttributeBinding.getContainer().seekEntityBinding().getPrimaryTable()
);
}
private ForeignKey createOrLocateForeignKey(
String foreignKeyName,
TableSpecification sourceTable,
TableSpecification targetTable) {
ForeignKey foreignKey = null;
if ( foreignKeyName == null ) {
// todo: for now lets assume we have to create it, but eventually we should look through the
// candidate foreign keys referencing targetTable also...
foreignKey = sourceTable.createForeignKey( targetTable, null );
} else {
foreignKey = sourceTable.locateForeignKey( foreignKeyName );
if ( foreignKey == null ) {
foreignKey = sourceTable.createForeignKey( targetTable, foreignKeyName );
}
}
return foreignKey;
}
private void bindForeignKeyColumns(
ForeignKey foreignKey,
SingularAttributeBinding sourceAttributeBinding,
SingularAttributeBinding targetAttributeBinding) {
final List<RelationalValueBinding> sourceValues = sourceAttributeBinding.getRelationalValueBindings();
final List<RelationalValueBinding> targetValues = targetAttributeBinding.getRelationalValueBindings();
if ( sourceValues.size() != targetValues.size() ) {
throw new MappingException(
String.format(
"Cannot create foreign key because number of columns in source attribute (%s) is not the same as the number of columns in the target attribute (%s).",
sourceAttributeBinding.getAttribute().getName(),
targetAttributeBinding.getAttribute().getName()
),
bindingContexts.peek().getOrigin()
);
}
for ( int i = 0 ; i < sourceValues.size() ; i++ ) {
final Value sourceValue = sourceValues.get( i ).getValue();
final Value targetValue = targetValues.get( i ).getValue();
if ( Column.class.isInstance( sourceValue ) ) {
Column sourceColumn = Column.class.cast( sourceValue );
if ( !Column.class.isInstance( targetValue ) ) {
throw new MappingException(
String.format(
"Cannot create foreign key for (%s) because the source value is a column (%s) and the corresponding value in the target attribute (%s) is a derived value",
sourceAttributeBinding.getAttribute().getName(),
sourceColumn.getColumnName(),
targetAttributeBinding.getAttribute().getName()
),
bindingContexts.peek().getOrigin()
);
}
foreignKey.addColumnMapping( Column.class.cast( sourceValue ), Column.class.cast( targetValue ) );
}
else {
if ( Column.class.isInstance( targetValue ) ) {
Column targetColumn = Column.class.cast( targetValue );
throw new MappingException(
String.format(
"Cannot create foreign key for (%s) because the source value is a derived value and the corresponding value in the target attribute (%s) is a column (%s).",
sourceAttributeBinding.getAttribute().getName(),
targetAttributeBinding.getAttribute().getName(),
targetColumn.getColumnName()
),
bindingContexts.peek().getOrigin()
);
}
throw new NotYetImplementedException(
"Derived values are not supported when creating a foreign key that targets attribute columns."
);
}
}
}
private void bindDiscriminator( final EntityBinding rootEntityBinding, final RootEntitySource rootEntitySource ) {
final DiscriminatorSource discriminatorSource = rootEntitySource.getDiscriminatorSource();
if ( discriminatorSource == null ) {
@ -714,12 +819,14 @@ private ManyToOneAttributeBinding bindManyToOneAttribute(
attribute = createSingularAttribute( attributeBindingContainer, attributeSource );
}
// TODO: figure out which table is used (could be secondary table...)
TableSpecification table = attributeBindingContainer.seekEntityBinding().getPrimaryTable();
final List< RelationalValueBinding > relationalValueBindings =
bindValues(
attributeBindingContainer,
attributeSource,
attribute,
attributeBindingContainer.seekEntityBinding().getPrimaryTable() );
table
);
org.hibernate.internal.util.Value< Class< ? >> referencedJavaTypeValue = createSingularAttributeJavaType( attribute );
final String referencedEntityName =
attributeSource.getReferencedEntityName() != null
@ -730,6 +837,17 @@ private ManyToOneAttributeBinding bindManyToOneAttribute(
attributeSource.getReferencedEntityAttributeName() == null
? referencedEntityBinding.getHierarchyDetails().getEntityIdentifier().getValueBinding()
: referencedEntityBinding.locateAttributeBinding( attributeSource.getReferencedEntityAttributeName() );
if ( ! referencedAttributeBinding.getAttribute().isSingular() ) {
throw new MappingException(
String.format( "Many-to-one attribute (%s) references a plural attribute (%s)",
attribute.getName(),
referencedAttributeBinding.getAttribute().getName()
),
bindingContexts.peek().getOrigin()
);
}
final SingularAttributeBinding singularReferencedAttributeBinding =
(SingularAttributeBinding) referencedAttributeBinding;
// todo : we should consider basing references on columns instead of property-ref, which would require a resolution (later) of property-ref to column names
final ManyToOneAttributeBinding attributeBinding =
attributeBindingContainer.makeManyToOneAttributeBinding(
@ -738,10 +856,18 @@ private ManyToOneAttributeBinding bindManyToOneAttribute(
attributeSource.isIncludedInOptimisticLocking(),
attributeSource.isLazy(),
createMetaAttributeContext( attributeBindingContainer, attributeSource ),
referencedAttributeBinding,
singularReferencedAttributeBinding,
relationalValueBindings );
// TODO: is this needed?
referencedAttributeBinding.addEntityReferencingAttributeBinding( attributeBinding );
// Foreign key...
ForeignKey foreignKey = createOrLocateForeignKey(
attributeSource.getForeignKeyName(),
attributeBinding,
singularReferencedAttributeBinding
);
bindForeignKeyColumns( foreignKey, attributeBinding, singularReferencedAttributeBinding );
// Type resolution...
if ( !attribute.isTypeResolved() ) {
attribute.resolveType( referencedEntityBinding.getEntity() );
}
@ -846,17 +972,9 @@ private void bindSecondaryTables( final EntityBinding entityBinding, final Entit
final TableSpecification table = createTable( secondaryTableSource.getTableSource(), null );
// todo: really need a concept like SecondaryTableSource in the binding model as well
// so that EntityBinding can know the proper foreign key to use to build SQL statements.
ForeignKey foreignKey = null;
if ( secondaryTableSource.getForeignKeyName() == null ) {
// todo: for now lets assume we have to create it, but eventually we should look through the
// candidate foreign keys referencing primary table also...
foreignKey = table.createForeignKey( primaryTable, null );
} else {
foreignKey = table.locateForeignKey( secondaryTableSource.getForeignKeyName() );
if ( foreignKey == null ) {
foreignKey = table.createForeignKey( primaryTable, secondaryTableSource.getForeignKeyName() );
}
}
ForeignKey foreignKey = createOrLocateForeignKey(
secondaryTableSource.getForeignKeyName(), table, primaryTable
);
for ( final PrimaryKeyJoinColumnSource joinColumnSource : secondaryTableSource.getJoinColumns() ) {
// todo : currently we only support columns here, not formulas
// todo : apply naming strategy to infer missing column name

View File

@ -115,7 +115,7 @@ public ManyToOneAttributeBinding makeManyToOneAttributeBinding(
boolean includedInOptimisticLocking,
boolean lazy,
MetaAttributeContext metaAttributeContext,
AttributeBinding referencedAttributeBinding,
SingularAttributeBinding referencedAttributeBinding,
List<RelationalValueBinding> valueBindings);
/**

View File

@ -185,7 +185,7 @@ public ManyToOneAttributeBinding makeManyToOneAttributeBinding(
boolean includedInOptimisticLocking,
boolean lazy,
MetaAttributeContext metaAttributeContext,
AttributeBinding referencedAttributeBinding,
SingularAttributeBinding referencedAttributeBinding,
List<RelationalValueBinding> valueBindings) {
final ManyToOneAttributeBinding binding = new ManyToOneAttributeBinding(
this,

View File

@ -531,7 +531,7 @@ public ManyToOneAttributeBinding makeManyToOneAttributeBinding(
boolean includedInOptimisticLocking,
boolean lazy,
MetaAttributeContext metaAttributeContext,
AttributeBinding referencedAttributeBinding,
SingularAttributeBinding referencedAttributeBinding,
List<RelationalValueBinding> valueBindings) {
final ManyToOneAttributeBinding binding = new ManyToOneAttributeBinding(
this,

View File

@ -47,7 +47,7 @@ public class ManyToOneAttributeBinding
private final List<RelationalValueBinding> relationalValueBindings;
private final AttributeBinding referencedAttributeBinding;
private final SingularAttributeBinding referencedAttributeBinding;
private boolean isLogicalOneToOne;
@ -62,7 +62,7 @@ public ManyToOneAttributeBinding(
boolean includedInOptimisticLocking,
boolean lazy,
MetaAttributeContext metaAttributeContext,
AttributeBinding referencedAttributeBinding,
SingularAttributeBinding referencedAttributeBinding,
List<RelationalValueBinding> relationalValueBindings) {
super(
container,
@ -196,38 +196,6 @@ public final EntityBinding getReferencedEntityBinding() {
return (EntityBinding) referencedAttributeBinding.getContainer();
}
// private void buildForeignKey() {
// // TODO: move this stuff to relational model
// ForeignKey foreignKey = getValue().getTable()
// .createForeignKey( referencedAttributeBinding.getValue().getTable(), foreignKeyName );
// Iterator<SimpleValue> referencingValueIterator = getSimpleValues().iterator();
// Iterator<SimpleValue> targetValueIterator = referencedAttributeBinding.getSimpleValues().iterator();
// while ( referencingValueIterator.hasNext() ) {
// if ( !targetValueIterator.hasNext() ) {
// // TODO: improve this message
// throw new MappingException(
// "number of values in many-to-one reference is greater than number of values in target"
// );
// }
// SimpleValue referencingValue = referencingValueIterator.next();
// SimpleValue targetValue = targetValueIterator.next();
// if ( Column.class.isInstance( referencingValue ) ) {
// if ( !Column.class.isInstance( targetValue ) ) {
// // TODO improve this message
// throw new MappingException( "referencing value is a column, but target is not a column" );
// }
// foreignKey.addColumnMapping( Column.class.cast( referencingValue ), Column.class.cast( targetValue ) );
// }
// else if ( Column.class.isInstance( targetValue ) ) {
// // TODO: improve this message
// throw new MappingException( "referencing value is not a column, but target is a column." );
// }
// }
// if ( targetValueIterator.hasNext() ) {
// throw new MappingException( "target value has more simple values than referencing value" );
// }
// }
//
// public void validate() {
// // can't check this until both the domain and relational states are initialized...
// if ( getCascadeTypes().contains( CascadeType.DELETE_ORPHAN ) ) {

View File

@ -24,6 +24,7 @@
package org.hibernate.metamodel.spi.binding;
import java.sql.Types;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@ -42,8 +43,10 @@
import org.hibernate.metamodel.spi.domain.SingularAttribute;
import org.hibernate.metamodel.internal.MetadataImpl;
import org.hibernate.metamodel.spi.relational.Column;
import org.hibernate.metamodel.spi.relational.ForeignKey;
import org.hibernate.metamodel.spi.relational.Identifier;
import org.hibernate.metamodel.spi.relational.JdbcDataType;
import org.hibernate.metamodel.spi.relational.TableSpecification;
import org.hibernate.metamodel.spi.relational.Value;
import org.hibernate.metamodel.spi.source.MetadataImplementor;
import org.hibernate.service.ServiceRegistry;
@ -133,7 +136,9 @@ public void testEntityWithManyToOneMapping() {
metadata,
entityWithManyToOneBinding,
attributeBinding,
simpleEntityBinding.getHierarchyDetails().getEntityIdentifier().getValueBinding(),
SingularAttributeBinding.class.cast(
simpleEntityBinding.getHierarchyDetails().getEntityIdentifier().getValueBinding()
),
"`simpleEntity`"
);
@ -141,7 +146,7 @@ public void testEntityWithManyToOneMapping() {
metadata,
entityWithManyToOneBinding,
entityWithManyToOneBinding.locateAttributeBinding( "simpleEntityFromPropertyRef" ),
simpleEntityBinding.locateAttributeBinding( "name" ),
SingularAttributeBinding.class.cast( simpleEntityBinding.locateAttributeBinding( "name" ) ),
"`simplename`"
);
}
@ -150,7 +155,7 @@ private void checkManyToOneAttributeBinding(
MetadataImplementor metadata,
EntityBinding entityWithManyToOneBinding,
AttributeBinding attributeBinding,
AttributeBinding referencedAttributeBinding,
SingularAttributeBinding referencedAttributeBinding,
String manyToOneColumnName) {
final EntityBinding referencedEntityBinding = referencedAttributeBinding.getContainer().seekEntityBinding();
final String referencedEntityName = referencedEntityBinding.getEntity().getName();
@ -223,6 +228,25 @@ private void checkManyToOneAttributeBinding(
assertEquals( referencedJdbcDataType.getTypeCode(), jdbcDataType.getTypeCode() );
assertEquals( referencedJdbcDataType.getJavaType(), jdbcDataType.getJavaType() );
assertEquals( referencedJdbcDataType.getTypeName(), jdbcDataType.getTypeName() );
// locate the foreignKey
boolean sourceColumnFound = false;
for ( ForeignKey fk : column.getTable().getForeignKeys() ) {
for ( Column sourceColumn : fk.getSourceColumns() ) {
if ( sourceColumn == column ) {
assertFalse( "source column not found in more than one foreign key", sourceColumnFound );
sourceColumnFound = true;
Iterator<Column> targetColumnIterator = fk.getTargetColumns().iterator();
assertTrue( targetColumnIterator.hasNext() );
assertSame(
referencedAttributeBinding.getRelationalValueBindings().iterator().next().getValue(),
targetColumnIterator.next()
);
assertFalse( targetColumnIterator.hasNext() );
}
}
}
assertTrue( "foreign key with specified source column found", sourceColumnFound );
}
@Test