diff --git a/src/org/hibernate/engine/StatefulPersistenceContext.java b/src/org/hibernate/engine/StatefulPersistenceContext.java index 8f5912a5a8..a10d994c32 100644 --- a/src/org/hibernate/engine/StatefulPersistenceContext.java +++ b/src/org/hibernate/engine/StatefulPersistenceContext.java @@ -1005,72 +1005,122 @@ public String toString() { } /** - * Search the persistence context for an owner for the child object, - * given a collection role. If mergeMap is non-null, also - * check the detached graph being merged for a parent. + * Search this persistence context for an associated entity instance which is considered the "owner" of + * the given childEntity, and return that owner's id value. This is performed in the scenario of a + * uni-directional, non-inverse one-to-many collection (which means that the collection elements do not maintain + * a direct reference to the owner). + *
+ * As such, the processing here is basically to loop over every entity currently associated with this persistence + * context and for those of the correct entity (sub) type to extract its collection role property value and see + * if the child is contained within that collection. If so, we have found the owner; if not, we go on. + * + * Also need to account for mergeMap which acts as a local copy cache managed for the duration of a merge + * operation. It represents a map of the detached entity instances pointing to the corresponding managed instance. + * + * @param entityName The entity name for the entity type which would own the child + * @param propertyName The name of the property on the owning entity type which would name this child association. + * @param childEntity The child entity instance for which to locate the owner instance id. + * @param mergeMap A map of non-persistent instances from an on-going merge operation (possibly null). + * + * @return The id of the entityName instance which is said to own the child; null if an appropriate owner not + * located. */ - public Serializable getOwnerId(String entity, String property, Object childEntity, Map mergeMap) { - - EntityPersister persister = session.getFactory() - .getEntityPersister(entity); - final CollectionPersister collectionPersister = session.getFactory() - .getCollectionPersister(entity + '.' + property); - + public Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) { + final String collectionRole = entityName + '.' + propertyName; + final EntityPersister persister = session.getFactory().getEntityPersister( entityName ); + final CollectionPersister collectionPersister = session.getFactory().getCollectionPersister( collectionRole ); + + // iterate all the entities currently associated with the persistence context. Iterator entities = entityEntries.entrySet().iterator(); while ( entities.hasNext() ) { - Map.Entry me = (Map.Entry) entities.next(); - EntityEntry ee = (EntityEntry) me.getValue(); - if ( persister.isSubclassEntityName( ee.getEntityName() ) ) { - Object instance = me.getKey(); + final Map.Entry me = ( Map.Entry ) entities.next(); + final EntityEntry entityEntry = ( EntityEntry ) me.getValue(); + // does this entity entry pertain to the entity persister in which we are interested (owner)? + if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { + final Object entityEntryInstance = me.getKey(); //check if the managed object is the parent - boolean found = isFoundInParent( - property, - childEntity, - persister, + boolean found = isFoundInParent( + propertyName, + childEntity, + persister, collectionPersister, - instance - ); + entityEntryInstance + ); - if (!found && mergeMap!=null) { + if ( !found && mergeMap != null ) { //check if the detached object being merged is the parent - Object unmergedInstance = mergeMap.get(instance); - Object unmergedChild = mergeMap.get(childEntity); - if ( unmergedInstance!=null && unmergedChild!=null ) { - found = isFoundInParent( - property, - unmergedChild, - persister, + Object unmergedInstance = mergeMap.get( entityEntryInstance ); + Object unmergedChild = mergeMap.get( childEntity ); + if ( unmergedInstance != null && unmergedChild != null ) { + found = isFoundInParent( + propertyName, + unmergedChild, + persister, collectionPersister, - unmergedInstance - ); + unmergedInstance + ); } } - + if ( found ) { - return ee.getId(); + return entityEntry.getId(); } - + } } + + // if we get here, it is possible that we have a proxy 'in the way' of the merge map resolution... + // NOTE: decided to put this here rather than in the above loop as I was nervous about the performance + // of the loop-in-loop especially considering this is far more likely the 'edge case' + if ( mergeMap != null ) { + Iterator mergeMapItr = mergeMap.entrySet().iterator(); + while ( mergeMapItr.hasNext() ) { + final Map.Entry mergeMapEntry = ( Map.Entry ) mergeMapItr.next(); + if ( mergeMapEntry.getKey() instanceof HibernateProxy ) { + final HibernateProxy proxy = ( HibernateProxy ) mergeMapEntry.getKey(); + if ( persister.isSubclassEntityName( proxy.getHibernateLazyInitializer().getEntityName() ) ) { + boolean found = isFoundInParent( + propertyName, + childEntity, + persister, + collectionPersister, + mergeMap.get( proxy ) + ); + if ( !found ) { + found = isFoundInParent( + propertyName, + mergeMap.get( childEntity ), + persister, + collectionPersister, + mergeMap.get( proxy ) + ); + } + if ( found ) { + return proxy.getHibernateLazyInitializer().getIdentifier(); + } + } + } + } + } + return null; } private boolean isFoundInParent( - String property, - Object childEntity, - EntityPersister persister, + String property, + Object childEntity, + EntityPersister persister, CollectionPersister collectionPersister, - Object potentialParent - ) { - Object collection = persister.getPropertyValue( - potentialParent, - property, - session.getEntityMode() - ); - return collection!=null && Hibernate.isInitialized(collection) && - collectionPersister.getCollectionType() - .contains(collection, childEntity, session); + Object potentialParent) { + Object collection = persister.getPropertyValue( + potentialParent, + property, + session.getEntityMode() + ); + return collection != null + && Hibernate.isInitialized( collection ) + && collectionPersister.getCollectionType().contains( collection, childEntity, session ); } /** diff --git a/src/org/hibernate/hql/ast/SqlASTFactory.java b/src/org/hibernate/hql/ast/SqlASTFactory.java index e97071f227..3d9cac6d90 100644 --- a/src/org/hibernate/hql/ast/SqlASTFactory.java +++ b/src/org/hibernate/hql/ast/SqlASTFactory.java @@ -1,13 +1,18 @@ // $Id$ package org.hibernate.hql.ast; +import java.lang.reflect.Constructor; + import antlr.ASTFactory; import antlr.Token; import antlr.collections.AST; + import org.hibernate.hql.antlr.HqlSqlTokenTypes; import org.hibernate.hql.ast.tree.AggregateNode; +import org.hibernate.hql.ast.tree.BetweenOperatorNode; import org.hibernate.hql.ast.tree.BinaryArithmeticOperatorNode; import org.hibernate.hql.ast.tree.BinaryLogicOperatorNode; +import org.hibernate.hql.ast.tree.BooleanLiteralNode; import org.hibernate.hql.ast.tree.Case2Node; import org.hibernate.hql.ast.tree.CaseNode; import org.hibernate.hql.ast.tree.CollectionFunction; @@ -19,10 +24,14 @@ import org.hibernate.hql.ast.tree.FromElement; import org.hibernate.hql.ast.tree.IdentNode; import org.hibernate.hql.ast.tree.ImpliedFromElement; +import org.hibernate.hql.ast.tree.InLogicOperatorNode; import org.hibernate.hql.ast.tree.IndexNode; import org.hibernate.hql.ast.tree.InitializeableNode; import org.hibernate.hql.ast.tree.InsertStatement; import org.hibernate.hql.ast.tree.IntoClause; +import org.hibernate.hql.ast.tree.IsNotNullLogicOperatorNode; +import org.hibernate.hql.ast.tree.IsNullLogicOperatorNode; +import org.hibernate.hql.ast.tree.JavaConstantNode; import org.hibernate.hql.ast.tree.LiteralNode; import org.hibernate.hql.ast.tree.MethodNode; import org.hibernate.hql.ast.tree.OrderByClause; @@ -30,18 +39,12 @@ import org.hibernate.hql.ast.tree.QueryNode; import org.hibernate.hql.ast.tree.SelectClause; import org.hibernate.hql.ast.tree.SelectExpressionImpl; +import org.hibernate.hql.ast.tree.SessionFactoryAwareNode; import org.hibernate.hql.ast.tree.SqlFragment; import org.hibernate.hql.ast.tree.SqlNode; import org.hibernate.hql.ast.tree.UnaryArithmeticNode; -import org.hibernate.hql.ast.tree.UpdateStatement; -import org.hibernate.hql.ast.tree.BetweenOperatorNode; import org.hibernate.hql.ast.tree.UnaryLogicOperatorNode; -import org.hibernate.hql.ast.tree.InLogicOperatorNode; -import org.hibernate.hql.ast.tree.JavaConstantNode; -import org.hibernate.hql.ast.tree.SessionFactoryAwareNode; -import org.hibernate.hql.ast.tree.BooleanLiteralNode; - -import java.lang.reflect.Constructor; +import org.hibernate.hql.ast.tree.UpdateStatement; /** * Custom AST factory the intermediate tree that causes ANTLR to create specialized @@ -157,7 +160,9 @@ public Class getASTNodeType(int tokenType) { case NOT_BETWEEN: return BetweenOperatorNode.class; case IS_NULL: + return IsNullLogicOperatorNode.class; case IS_NOT_NULL: + return IsNotNullLogicOperatorNode.class; case EXISTS: return UnaryLogicOperatorNode.class; default: diff --git a/src/org/hibernate/hql/ast/tree/AbstractNullnessCheckNode.java b/src/org/hibernate/hql/ast/tree/AbstractNullnessCheckNode.java new file mode 100644 index 0000000000..f366492d5b --- /dev/null +++ b/src/org/hibernate/hql/ast/tree/AbstractNullnessCheckNode.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Steve Ebersole + */ +package org.hibernate.hql.ast.tree; + +import antlr.collections.AST; + +import org.hibernate.type.Type; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.hql.antlr.HqlSqlTokenTypes; +import org.hibernate.util.StringHelper; +import org.hibernate.HibernateException; + +/** + * AbstractNullnessCheckNode implementation + * + * @author Steve Ebersole + */ +public abstract class AbstractNullnessCheckNode extends UnaryLogicOperatorNode { + public void initialize() { + // TODO : this really needs to be delayed unitl after we definitively know the operand node type; + // where this is currently a problem is parameters for which where we cannot unequivocally + // resolve an expected type + Type operandType = extractDataType( getOperand() ); + if ( operandType == null ) { + return; + } + SessionFactoryImplementor sessionFactory = getSessionFactoryHelper().getFactory(); + int operandColumnSpan = operandType.getColumnSpan( sessionFactory ); + if ( operandColumnSpan > 1 ) { + mutateRowValueConstructorSyntax( operandColumnSpan ); + } + } + + protected abstract int getExpansionConnectorType(); + protected abstract String getExpansionConnectorText(); + + private void mutateRowValueConstructorSyntax(int operandColumnSpan) { + final int comparisonType = getType(); + final String comparisonText = getText(); + + final int expansionConnectorType = getExpansionConnectorType(); + final String expansionConnectorText = getExpansionConnectorText(); + + setType( expansionConnectorType ); + setText( expansionConnectorText ); + + String[] mutationTexts = extractMutationTexts( getOperand(), operandColumnSpan ); + + AST container = this; + for ( int i = operandColumnSpan - 1; i > 0; i-- ) { + if ( i == 1 ) { + AST op1 = getASTFactory().create( comparisonType, comparisonText ); + AST operand1 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, mutationTexts[0] ); + op1.setFirstChild( operand1 ); + container.setFirstChild( op1 ); + AST op2 = getASTFactory().create( comparisonType, comparisonText ); + AST operand2 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, mutationTexts[1] ); + op2.setFirstChild( operand2 ); + op1.setNextSibling( op2 ); + } + else { + AST op = getASTFactory().create( comparisonType, comparisonText ); + AST operand = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, mutationTexts[i] ); + op.setFirstChild( operand ); + AST newContainer = getASTFactory().create( expansionConnectorType, expansionConnectorText ); + container.setFirstChild( newContainer ); + newContainer.setNextSibling( op ); + container = newContainer; + } + } + } + + protected Type extractDataType(Node operand) { + Type type = null; + if ( operand instanceof SqlNode ) { + type = ( ( SqlNode ) operand ).getDataType(); + } + if ( type == null && operand instanceof ExpectedTypeAwareNode ) { + type = ( ( ExpectedTypeAwareNode ) operand ).getExpectedType(); + } + return type; + } + + private static String[] extractMutationTexts(Node operand, int count) { + if ( operand instanceof ParameterNode ) { + String[] rtn = new String[count]; + for ( int i = 0; i < count; i++ ) { + rtn[i] = "?"; + } + return rtn; + } + else if ( operand.getType() == HqlSqlTokenTypes.VECTOR_EXPR ) { + String[] rtn = new String[ operand.getNumberOfChildren() ]; + int x = 0; + AST node = operand.getFirstChild(); + while ( node != null ) { + rtn[ x++ ] = node.getText(); + node = node.getNextSibling(); + } + return rtn; + } + else if ( operand instanceof SqlNode ) { + String nodeText = operand.getText(); + if ( nodeText.startsWith( "(" ) ) { + nodeText = nodeText.substring( 1 ); + } + if ( nodeText.endsWith( ")" ) ) { + nodeText = nodeText.substring( 0, nodeText.length() - 1 ); + } + String[] splits = StringHelper.split( ", ", nodeText ); + if ( count != splits.length ) { + throw new HibernateException( "SqlNode's text did not reference expected number of columns" ); + } + return splits; + } + else { + throw new HibernateException( "dont know how to extract row value elements from node : " + operand ); + } + } +} diff --git a/src/org/hibernate/hql/ast/tree/IsNotNullLogicOperatorNode.java b/src/org/hibernate/hql/ast/tree/IsNotNullLogicOperatorNode.java new file mode 100644 index 0000000000..30a816fe51 --- /dev/null +++ b/src/org/hibernate/hql/ast/tree/IsNotNullLogicOperatorNode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Steve Ebersole + */ +package org.hibernate.hql.ast.tree; + +import org.hibernate.hql.antlr.HqlSqlTokenTypes; + +/** + * IsNotNullLogicOperatorNode implementation + * + * @author Steve Ebersole + */ +public class IsNotNullLogicOperatorNode extends AbstractNullnessCheckNode { + protected int getExpansionConnectorType() { + return HqlSqlTokenTypes.OR; + } + + protected String getExpansionConnectorText() { + return "OR"; + } +} diff --git a/src/org/hibernate/hql/ast/tree/IsNullLogicOperatorNode.java b/src/org/hibernate/hql/ast/tree/IsNullLogicOperatorNode.java new file mode 100644 index 0000000000..71c99fb56a --- /dev/null +++ b/src/org/hibernate/hql/ast/tree/IsNullLogicOperatorNode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Steve Ebersole + */ +package org.hibernate.hql.ast.tree; + +import org.hibernate.hql.antlr.HqlSqlTokenTypes; + +/** + * Represents a 'is null' check. + * + * @author Steve Ebersole + */ +public class IsNullLogicOperatorNode extends AbstractNullnessCheckNode { + protected int getExpansionConnectorType() { + return HqlSqlTokenTypes.AND; + } + + protected String getExpansionConnectorText() { + return "AND"; + } +} diff --git a/src/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java b/src/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java index 0064e9adf7..842edfecbd 100644 --- a/src/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java +++ b/src/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java @@ -4,9 +4,11 @@ import org.hibernate.Hibernate; /** - * @author Steve Ebersole + * Represents a unary operator node. + * + * @author Steve Ebersole */ -public class UnaryLogicOperatorNode extends SqlNode implements UnaryOperatorNode { +public class UnaryLogicOperatorNode extends HqlSqlWalkerNode implements UnaryOperatorNode { public Node getOperand() { return ( Node ) getFirstChild(); } diff --git a/src/org/hibernate/property/BackrefPropertyAccessor.java b/src/org/hibernate/property/BackrefPropertyAccessor.java index 5e7dc523ce..21029df25b 100755 --- a/src/org/hibernate/property/BackrefPropertyAccessor.java +++ b/src/org/hibernate/property/BackrefPropertyAccessor.java @@ -1,67 +1,112 @@ -//$Id$ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Gavin King, Steve Ebersole + */ package org.hibernate.property; +import java.io.Serializable; import java.lang.reflect.Method; import java.util.Map; -import java.io.Serializable; -import org.hibernate.HibernateException; -import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; /** - * Represents a "back-reference" to the id of a collection owner. + * Represents a "back-reference" to the id of a collection owner. A "back-reference" is pertinent in mapping scenarios + * where we have a uni-directional one-to-many association in which only the many side is mapped. In this case it is + * the collection itself which is responsible for the FK value. + * + * In this scenario, the one side has no inherent knowledge of its "owner". So we introduce a synthetic property into + * the one side to represent the association; a so-called back-reference. * * @author Gavin King + * @author Steve Ebersole */ public class BackrefPropertyAccessor implements PropertyAccessor { private final String propertyName; private final String entityName; + // cache these since they are stateless + private final BackrefSetter setter; // this one could even be static... + private final BackrefGetter getter; + /** * A placeholder for a property value, indicating that * we don't know the value of the back reference */ public static final Serializable UNKNOWN = new Serializable() { - public String toString() { return "