Author: ste...@jb... Date: 2006-02-28 23:24:08 -0500 (Tue, 28 Feb 2006) New Revision: 9528 Added: branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/NodeTraverser.java branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/WithClauseTest.java Removed: branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/AdHocOnTest.java Modified: branches/Branch_3_1/Hibernate3/src/org/hibernate/engine/JoinSequence.java branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/HqlSqlWalker.java branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/ASTUtil.java branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/JoinProcessor.java branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/HQLSuite.java Log: HHH-1433 plus other hql-with-clause improvements backported from 3.2 Modified: branches/Branch_3_1/Hibernate3/src/org/hibernate/engine/JoinSequence.java =================================================================== --- branches/Branch_3_1/Hibernate3/src/org/hibernate/engine/JoinSequence.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/src/org/hibernate/engine/JoinSequence.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -165,7 +165,9 @@ else { condition = on; } - if ( extraOnClause != null ) { + if ( extraOnClause != null && !isManyToManyRoot( join.getJoinable() ) ) { + // the isManyToManyRoot() check ensures we apply the extra on clause only to + // the target table in the case of a many-to-many join condition += " and " + extraOnClause; } joinFragment.addJoin( Modified: branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/HqlSqlWalker.java =================================================================== --- branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/HqlSqlWalker.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/HqlSqlWalker.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -14,6 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.QueryException; +import org.hibernate.HibernateException; import org.hibernate.engine.JoinSequence; import org.hibernate.engine.ParameterBinder; import org.hibernate.engine.SessionFactoryImplementor; @@ -51,6 +52,7 @@ import org.hibernate.hql.ast.util.LiteralProcessor; import org.hibernate.hql.ast.util.SessionFactoryHelper; import org.hibernate.hql.ast.util.SyntheticAndFactory; +import org.hibernate.hql.ast.util.NodeTraverser; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.SequenceGenerator; @@ -331,15 +333,78 @@ try { withClause( hqlWithNode ); AST hqlSqlWithNode = returnAST; + if ( log.isDebugEnabled() ) { + log.debug( "handleWithFragment() : " + getASTPrinter().showAsString( hqlSqlWithNode, "-- with clause --" ) ); + } + WithClauseVisitor visitor = new WithClauseVisitor(); + NodeTraverser traverser = new NodeTraverser( visitor ); + traverser.traverseDepthFirst( hqlSqlWithNode ); + FromElement referencedFromElement = visitor.getReferencedFromElement(); + if ( referencedFromElement != fromElement ) { + throw new SemanticException( "with-clause expressions did not reference from-clause element to which the with-clause was associated" ); + } SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() ); sql.whereExpr( hqlSqlWithNode.getFirstChild() ); - fromElement.setAdHocOnClauseFragment( "(" + sql.getSQL() + ")" ); + fromElement.setWithClauseFragment( "(" + sql.getSQL() + ")" ); } catch ( Exception e) { throw new SemanticException( e.getMessage() ); } } + private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy { + private FromElement referencedFromElement; + private String joinAlias; + + public void visit(AST node) { + // todo : currently expects that the individual with expressions apply to the same sql table join. + // This may not be the case for joined-subclass where the property values + // might be coming from different tables in the joined hierarchy. At some + // point we should expand this to support that capability. However, that has + // some difficulties: + // 1) the biggest is how to handle ORs when the individual comparisons are + // linked to different sql joins. + // 2) here we would need to track each comparison individually, along with + // the join alias to which it applies and then pass that information + // back to the FromElement so it can pass it along to the JoinSequence + + if ( node instanceof DotNode ) { + DotNode dotNode = ( DotNode ) node; + FromElement fromElement = dotNode.getFromElement(); + if ( referencedFromElement != null ) { + if ( fromElement != referencedFromElement ) { + throw new HibernateException( "with-clause referenced two different from-clause elements" ); + } + } + else { + referencedFromElement = fromElement; + joinAlias = extractAppliedAlias( dotNode ); + // todo : temporary + // needed because currently persister is the one that + // creates and renders the join fragments for inheritence + // hierarchies... + if ( !joinAlias.equals( referencedFromElement.getTableAlias() ) ) { + throw new HibernateException( "with clause can only reference columns in the driving table" ); + } + } + } + } + + private String extractAppliedAlias(DotNode dotNode) { + return dotNode.getText().substring( 0, dotNode.getText().indexOf( '.' ) ); + } + + public FromElement getReferencedFromElement() { + return referencedFromElement; + } + + public String getJoinAlias() { + // later, can be used to help resolve HHH-1521 + // also see "driving table" exception above + return joinAlias; + } + } + /** * Sets the current 'FROM' context. * Modified: branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java =================================================================== --- branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -58,7 +58,7 @@ private boolean useWhereFragment = true; private List destinations = new LinkedList(); private boolean manyToMany = false; - private String adHocOnClauseFragment = null; + private String withClauseFragment = null; public FromElement() { } @@ -478,12 +478,12 @@ isAllPropertyFetch = fetch; } - public String getAdHocOnClauseFragment() { - return adHocOnClauseFragment; + public String getWithClauseFragment() { + return withClauseFragment; } - public void setAdHocOnClauseFragment(String adHocOnClauseFragment) { - this.adHocOnClauseFragment = adHocOnClauseFragment; + public void setWithClauseFragment(String withClauseFragment) { + this.withClauseFragment = withClauseFragment; } public boolean hasCacheablePersister() { Modified: branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/ASTUtil.java =================================================================== --- branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/ASTUtil.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/ASTUtil.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -273,18 +273,31 @@ } public static List collectChildren(AST root, FilterPredicate predicate) { - List children = new ArrayList(); - collectChildren( children, root, predicate ); - return children; + return new CollectingNodeVisitor( predicate ).collect( root ); } - private static void collectChildren(List children, AST root, FilterPredicate predicate) { - for ( AST n = root.getFirstChild(); n != null; n = n.getNextSibling() ) { - if ( predicate == null || !predicate.exclude( n ) ) { - children.add( n ); + private static class CollectingNodeVisitor implements NodeTraverser.VisitationStrategy { + private final FilterPredicate predicate; + private final List collectedNodes = new ArrayList(); + + public CollectingNodeVisitor(FilterPredicate predicate) { + this.predicate = predicate; + } + + public void visit(AST node) { + if ( predicate == null || !predicate.exclude( node ) ) { + collectedNodes.add( node ); } - collectChildren( children, n, predicate ); } + + public List getCollectedNodes() { + return collectedNodes; + } + + public List collect(AST root) { + NodeTraverser traverser = new NodeTraverser( this ); + traverser.traverseDepthFirst( root ); + return collectedNodes; + } } - } Modified: branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/JoinProcessor.java =================================================================== --- branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/JoinProcessor.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/JoinProcessor.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -107,7 +107,7 @@ JoinFragment joinFragment = join.toJoinFragment( inSubquery ? Collections.EMPTY_MAP : queryTranslatorImpl.getEnabledFilters(), fromElement.useFromFragment(), - fromElement.getAdHocOnClauseFragment() + fromElement.getWithClauseFragment() ); String frag = joinFragment.toFromFragmentString(); Added: branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/NodeTraverser.java =================================================================== --- branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/NodeTraverser.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/src/org/hibernate/hql/ast/util/NodeTraverser.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -0,0 +1,44 @@ +package org.hibernate.hql.ast.util; + +import antlr.collections.AST; + +/** + * A visitor for traversing an AST tree. + * + * @author Steve Ebersole + */ +public class NodeTraverser { + public static interface VisitationStrategy { + public void visit(AST node); + } + + private final VisitationStrategy strategy; + + public NodeTraverser(VisitationStrategy strategy) { + this.strategy = strategy; + } + + /** + * Traverse the AST tree depth first. + * <p/> + * Note that the AST passed in is not visited itself. Visitation starts + * with its children. + * + * @param ast + */ + public void traverseDepthFirst(AST ast) { + if ( ast == null ) { + throw new IllegalArgumentException( "node to traverse cannot be null!" ); + } + visitDepthFirst( ast.getFirstChild() ); + } + + private void visitDepthFirst(AST ast) { + if ( ast == null ) { + return; + } + strategy.visit( ast ); + visitDepthFirst( ast.getFirstChild() ); + visitDepthFirst( ast.getNextSibling() ); + } +} Deleted: branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/AdHocOnTest.java =================================================================== --- branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/AdHocOnTest.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/AdHocOnTest.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -1,129 +0,0 @@ -// $Id$ -package org.hibernate.test.hql; - -import org.hibernate.test.TestCase; -import org.hibernate.Session; -import org.hibernate.Transaction; -import org.hibernate.HibernateException; - -import java.util.List; - -import junit.framework.Test; -import junit.framework.TestSuite; - -/** - * Implementation of AdHocOnTest. - * - * @author Steve Ebersole - */ -public class AdHocOnTest extends TestCase { - - public AdHocOnTest(String name) { - super( name ); - } - - protected String[] getMappings() { - return new String[] { "hql/Animal.hbm.xml" }; - } - - public static Test suite() { - return new TestSuite( AdHocOnTest.class ); - } - - public void testAdHocOnFailsWithFetch() { - TestData data = new TestData(); - data.prepare(); - - Session s = openSession(); - Transaction txn = s.beginTransaction(); - - try { - s.createQuery( "from Animal a inner join fetch a.offspring as o with o.bodyWeight = :someLimit" ) - .setDouble( "someLimit", 1 ) - .list(); - fail( "ad-hoc on clause allowed with fetched association" ); - } - catch ( HibernateException e ) { - System.out.println( "TEST (OK) : " + e.getMessage() ); - // the expected response... - } - - txn.commit(); - s.close(); - - data.cleanup(); - } - - public void testAdHocOn() { - TestData data = new TestData(); - data.prepare(); - - Session s = openSession(); - Transaction txn = s.beginTransaction(); - - List list = s.createQuery( "from Animal a inner join a.offspring as o with o.bodyWeight < :someLimit" ) - .setDouble( "someLimit", 1 ) - .list(); - assertTrue( "ad-hoc on did not take effect", list.isEmpty() ); - - list = s.createQuery( "from Animal a inner join a.mother as m with m.bodyWeight < :someLimit" ) - .setDouble( "someLimit", 1 ) - .list(); - assertTrue( "ad-hoc on did not take effect", list.isEmpty() ); - - txn.commit(); - s.close(); - - data.cleanup(); - } - - private class TestData { - public void prepare() { - Session session = openSession(); - Transaction txn = session.beginTransaction(); - - Animal mother = new Animal(); - mother.setBodyWeight( 10 ); - mother.setDescription( "mother" ); - - Animal father = new Animal(); - father.setBodyWeight( 15 ); - father.setDescription( "father" ); - - Animal child1 = new Animal(); - child1.setBodyWeight( 5 ); - child1.setDescription( "child1" ); - - Animal child2 = new Animal(); - child2.setBodyWeight( 6 ); - child2.setDescription( "child2" ); - - child1.setMother( mother ); - child1.setFather( father ); - mother.addOffspring( child1 ); - father.addOffspring( child1 ); - - child2.setMother( mother ); - child2.setFather( father ); - mother.addOffspring( child2 ); - father.addOffspring( child2 ); - - session.save( mother ); - session.save( father ); - session.save( child1 ); - session.save( child2 ); - - txn.commit(); - session.close(); - } - - public void cleanup() { - Session session = openSession(); - Transaction txn = session.beginTransaction(); - session.createQuery( "delete Animal where mother is not null" ).executeUpdate(); - session.createQuery( "delete Animal" ).executeUpdate(); - txn.commit(); - session.close(); - } - } -} Modified: branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/HQLSuite.java =================================================================== --- branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/HQLSuite.java 2006-03-01 03:49:49 UTC (rev 9527) +++ branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/HQLSuite.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -16,7 +16,7 @@ suite.addTest( HQLTest.suite() ); suite.addTest( ASTParserLoadingTest.suite() ); suite.addTest( BulkManipulationTest.suite() ); - suite.addTest( AdHocOnTest.suite() ); + suite.addTest( WithClauseTest.suite() ); // suite.addTest( ASTQueryTranslatorTest.suite() ); suite.addTest( EJBQLTest.suite() ); suite.addTest( HqlParserTest.suite() ); Copied: branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/WithClauseTest.java (from rev 9520, branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/AdHocOnTest.java) =================================================================== --- branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/AdHocOnTest.java 2006-02-28 17:59:02 UTC (rev 9520) +++ branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/WithClauseTest.java 2006-03-01 04:24:08 UTC (rev 9528) @@ -0,0 +1,199 @@ +// $Id$ +package org.hibernate.test.hql; + +import org.hibernate.test.TestCase; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.HibernateException; +import org.hibernate.QueryException; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Implementation of WithClauseTest. + * + * @author Steve Ebersole + */ +public class WithClauseTest extends TestCase { + + public WithClauseTest(String name) { + super( name ); + } + + protected String[] getMappings() { + return new String[] { "hql/Animal.hbm.xml" }; + } + + public static Test suite() { + return new TestSuite( WithClauseTest.class ); + } + + public void testWithClauseFailsWithFetch() { + TestData data = new TestData(); + data.prepare(); + + Session s = openSession(); + Transaction txn = s.beginTransaction(); + + try { + s.createQuery( "from Animal a inner join fetch a.offspring as o with o.bodyWeight = :someLimit" ) + .setDouble( "someLimit", 1 ) + .list(); + fail( "ad-hoc on clause allowed with fetched association" ); + } + catch ( HibernateException e ) { + System.out.println( "TEST (OK) : " + e.getMessage() ); + // the expected response... + } + + txn.commit(); + s.close(); + + data.cleanup(); + } + + public void testInvalidWithSemantics() { + Session s = openSession(); + Transaction txn = s.beginTransaction(); + + try { + // PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f' + // alias relates to the Human.friends collection which the aonther Human entity. The issue + // here is the way JoinSequence and Joinable (the persister) interact to generate the + // joins relating to the sublcass/superclass tables + s.createQuery( "from Human h inner join h.friends as f with f.bodyWeight < :someLimit" ) + .setDouble( "someLimit", 1 ) + .list(); + fail( "failure expected" ); + } + catch( QueryException qe ) { + if ( qe.getMessage().indexOf( "can only reference columns in the driving table" ) < 0 ) { + fail( "unexpected failure type [" + qe.getMessage() + "]" ); + } + } + + try { + s.createQuery( "from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1" ) + .list(); + fail( "failure expected" ); + } + catch( QueryException qe ) { + if ( qe.getMessage().indexOf( "with-clause expressions did not reference from-clause element to which the with-clause was associated" ) < 0 ) { + fail( "unexpected failure type [" + qe.getMessage() + "]" ); + } + } + + try { + s.createQuery( "from Human h inner join h.offspring o with o.mother.father = :cousin" ) + .setEntity( "cousin", s.load( Human.class, new Long(123) ) ) + .list(); + fail( "failure expected" ); + } + catch( QueryException qe ) { + if ( qe.getMessage().indexOf( "with-clause expressions did not reference from-clause element to which the with-clause was associated" ) < 0 ) { + fail( "unexpected failure type [" + qe.getMessage() + "]" ); + } + } + + txn.commit(); + s.close(); + } + + public void testWithClause() { + TestData data = new TestData(); + data.prepare(); + + Session s = openSession(); + Transaction txn = s.beginTransaction(); + + // one-to-many + List list = s.createQuery( "from Human h inner join h.offspring as o with o.bodyWeight < :someLimit" ) + .setDouble( "someLimit", 1 ) + .list(); + assertTrue( "ad-hoc on did not take effect", list.isEmpty() ); + + // many-to-one + list = s.createQuery( "from Animal a inner join a.mother as m with m.bodyWeight < :someLimit" ) + .setDouble( "someLimit", 1 ) + .list(); + assertTrue( "ad-hoc on did not take effect", list.isEmpty() ); + + // many-to-many + list = s.createQuery( "from Human h inner join h.friends as f with f.nickName like 'bubba'" ) + .list(); + assertTrue( "ad-hoc on did not take effect", list.isEmpty() ); + + txn.commit(); + s.close(); + + data.cleanup(); + } + + private class TestData { + public void prepare() { + Session session = openSession(); + Transaction txn = session.beginTransaction(); + + Human mother = new Human(); + mother.setBodyWeight( 10 ); + mother.setDescription( "mother" ); + + Human father = new Human(); + father.setBodyWeight( 15 ); + father.setDescription( "father" ); + + Human child1 = new Human(); + child1.setBodyWeight( 5 ); + child1.setDescription( "child1" ); + + Human child2 = new Human(); + child2.setBodyWeight( 6 ); + child2.setDescription( "child2" ); + + Human friend = new Human(); + friend.setBodyWeight( 20 ); + friend.setDescription( "friend" ); + + child1.setMother( mother ); + child1.setFather( father ); + mother.addOffspring( child1 ); + father.addOffspring( child1 ); + + child2.setMother( mother ); + child2.setFather( father ); + mother.addOffspring( child2 ); + father.addOffspring( child2 ); + + father.setFriends( new ArrayList() ); + father.getFriends().add( friend ); + + session.save( mother ); + session.save( father ); + session.save( child1 ); + session.save( child2 ); + session.save( friend ); + + txn.commit(); + session.close(); + } + + public void cleanup() { + Session session = openSession(); + Transaction txn = session.beginTransaction(); + session.createQuery( "delete Animal where mother is not null" ).executeUpdate(); + List humansWithFriends = session.createQuery( "from Human h where exists(from h.friends)" ).list(); + Iterator itr = humansWithFriends.iterator(); + while ( itr.hasNext() ) { + session.delete( itr.next() ); + } + session.createQuery( "delete Animal" ).executeUpdate(); + txn.commit(); + session.close(); + } + } +} Property changes on: branches/Branch_3_1/Hibernate3/test/org/hibernate/test/hql/WithClauseTest.java ___________________________________________________________________ Name: svn:keywords + Author Date Id Revision Name: svn:eol-style + native |