Test and fix for HHH-9329
This commit is contained in:
parent
de7ca7882b
commit
1d823ceb76
|
@ -6,12 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.engine.internal;
|
package org.hibernate.engine.internal;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.regex.Matcher;
|
||||||
import java.util.List;
|
import java.util.regex.Pattern;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
@ -169,7 +167,19 @@ public class JoinSequence {
|
||||||
boolean includeAllSubclassJoins,
|
boolean includeAllSubclassJoins,
|
||||||
String withClauseFragment,
|
String withClauseFragment,
|
||||||
String withClauseJoinAlias) throws MappingException {
|
String withClauseJoinAlias) throws MappingException {
|
||||||
|
return toJoinFragment( enabledFilters, includeAllSubclassJoins, true, withClauseFragment, withClauseJoinAlias );
|
||||||
|
}
|
||||||
|
|
||||||
|
public JoinFragment toJoinFragment(
|
||||||
|
Map enabledFilters,
|
||||||
|
boolean includeAllSubclassJoins,
|
||||||
|
boolean renderSubclassJoins,
|
||||||
|
String withClauseFragment,
|
||||||
|
String withClauseJoinAlias) throws MappingException {
|
||||||
final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
|
final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
|
||||||
|
Iterator<Join> iter;
|
||||||
|
Join first;
|
||||||
|
Joinable last;
|
||||||
if ( rootJoinable != null ) {
|
if ( rootJoinable != null ) {
|
||||||
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
|
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
|
||||||
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters, treatAsDeclarations );
|
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters, treatAsDeclarations );
|
||||||
|
@ -178,10 +188,111 @@ public class JoinSequence {
|
||||||
// of that fact.
|
// of that fact.
|
||||||
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
|
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
|
||||||
addSubclassJoins( joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins, treatAsDeclarations );
|
addSubclassJoins( joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins, treatAsDeclarations );
|
||||||
|
|
||||||
|
last = rootJoinable;
|
||||||
}
|
}
|
||||||
|
else if (
|
||||||
|
withClauseFragment != null
|
||||||
|
&& joins.size() > 1
|
||||||
|
&& withClauseFragment.contains( withClauseJoinAlias )
|
||||||
|
&& ( first = ( iter = joins.iterator() ).next() ).joinType == JoinType.LEFT_OUTER_JOIN
|
||||||
|
) {
|
||||||
|
final QueryJoinFragment subqueryJoinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( "(select " );
|
||||||
|
|
||||||
Joinable last = rootJoinable;
|
subqueryJoinFragment.addFromFragmentString( first.getAlias() );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( ".*" );
|
||||||
|
|
||||||
|
// Re-alias columns of withClauseJoinAlias and rewrite withClauseFragment
|
||||||
|
Pattern p = Pattern.compile( Pattern.quote( withClauseJoinAlias + "." ) + "([a-zA-Z0-9_]+)");
|
||||||
|
Matcher matcher = p.matcher( withClauseFragment );
|
||||||
|
StringBuilder withClauseSb = new StringBuilder( withClauseFragment.length() );
|
||||||
|
withClauseSb.append( " and " );
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int aliasNumber = 0;
|
||||||
|
while ( matcher.find() ) {
|
||||||
|
final String column = matcher.group( 1 );
|
||||||
|
final String alias = "_" + aliasNumber + "_" + column;
|
||||||
|
withClauseSb.append( withClauseFragment, start, matcher.start() );
|
||||||
|
withClauseSb.append( first.getAlias() );
|
||||||
|
withClauseSb.append( '.' );
|
||||||
|
withClauseSb.append( alias );
|
||||||
|
|
||||||
|
subqueryJoinFragment.addFromFragmentString( ", " );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( withClauseJoinAlias );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( "." );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( column );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( " as " );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( alias );
|
||||||
|
|
||||||
|
start = matcher.end();
|
||||||
|
aliasNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
withClauseSb.append( withClauseFragment, start, withClauseFragment.length() );
|
||||||
|
|
||||||
|
subqueryJoinFragment.addFromFragmentString( " from " );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( first.joinable.getTableName() );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( " " );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( first.getAlias() );
|
||||||
|
|
||||||
|
// Render following join sequences in a sub-sequence
|
||||||
|
JoinSequence subSequence = new JoinSequence( factory );
|
||||||
|
|
||||||
|
while ( iter.hasNext() ) {
|
||||||
|
Join join = iter.next();
|
||||||
|
subSequence.joins.add( join );
|
||||||
|
}
|
||||||
|
|
||||||
|
JoinFragment subFragment = subSequence.toJoinFragment(
|
||||||
|
enabledFilters,
|
||||||
|
false,
|
||||||
|
true, // TODO: only join subclasses that are needed for ON clause
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
subqueryJoinFragment.addFragment( subFragment );
|
||||||
|
subqueryJoinFragment.addFromFragmentString( ")" );
|
||||||
|
|
||||||
|
joinFragment.addJoin(
|
||||||
|
subqueryJoinFragment.toFromFragmentString(),
|
||||||
|
first.getAlias(),
|
||||||
|
first.getLHSColumns(),
|
||||||
|
JoinHelper.getRHSColumnNames( first.getAssociationType(), factory ),
|
||||||
|
first.joinType,
|
||||||
|
withClauseSb.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
for ( Join join : joins ) {
|
||||||
|
// Skip joining the first join node as it is contained in the subquery
|
||||||
|
if ( join != first ) {
|
||||||
|
joinFragment.addJoin(
|
||||||
|
join.getJoinable().getTableName(),
|
||||||
|
join.getAlias(),
|
||||||
|
join.getLHSColumns(),
|
||||||
|
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
|
||||||
|
join.joinType,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
addSubclassJoins(
|
||||||
|
joinFragment,
|
||||||
|
join.getAlias(),
|
||||||
|
join.getJoinable(),
|
||||||
|
join.joinType == JoinType.INNER_JOIN,
|
||||||
|
includeAllSubclassJoins,
|
||||||
|
// ugh.. this is needed because of how HQL parser (FromElementFactory/SessionFactoryHelper)
|
||||||
|
// builds the JoinSequence for HQL joins
|
||||||
|
treatAsDeclarations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinFragment;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
last = null;
|
||||||
|
}
|
||||||
for ( Join join : joins ) {
|
for ( Join join : joins ) {
|
||||||
// technically the treatAsDeclarations should only apply to rootJoinable or to a single Join,
|
// technically the treatAsDeclarations should only apply to rootJoinable or to a single Join,
|
||||||
// but that is not possible atm given how these JoinSequence and Join objects are built.
|
// but that is not possible atm given how these JoinSequence and Join objects are built.
|
||||||
|
@ -221,16 +332,19 @@ public class JoinSequence {
|
||||||
condition
|
condition
|
||||||
);
|
);
|
||||||
|
|
||||||
addSubclassJoins(
|
if (renderSubclassJoins) {
|
||||||
joinFragment,
|
addSubclassJoins(
|
||||||
join.getAlias(),
|
joinFragment,
|
||||||
join.getJoinable(),
|
join.getAlias(),
|
||||||
join.joinType == JoinType.INNER_JOIN,
|
join.getJoinable(),
|
||||||
includeAllSubclassJoins,
|
join.joinType == JoinType.INNER_JOIN,
|
||||||
// ugh.. this is needed because of how HQL parser (FromElementFactory/SessionFactoryHelper)
|
includeAllSubclassJoins,
|
||||||
// builds the JoinSequence for HQL joins
|
// ugh.. this is needed because of how HQL parser (FromElementFactory/SessionFactoryHelper)
|
||||||
treatAsDeclarations
|
// builds the JoinSequence for HQL joins
|
||||||
);
|
treatAsDeclarations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
last = join.getJoinable();
|
last = join.getJoinable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,6 +177,27 @@ public class WithClauseTest extends BaseCoreFunctionalTestCase {
|
||||||
s.close();
|
s.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-9329")
|
||||||
|
public void testWithClauseAsSubquery() {
|
||||||
|
TestData data = new TestData();
|
||||||
|
data.prepare();
|
||||||
|
|
||||||
|
Session s = openSession();
|
||||||
|
Transaction txn = s.beginTransaction();
|
||||||
|
|
||||||
|
// Since friends has a join table, we will first left join all friends and then do the WITH clause on the target entity table join
|
||||||
|
// Normally this produces 2 results which is wrong and can only be circumvented by converting the join table and target entity table join to a subquery
|
||||||
|
List list = s.createQuery( "from Human h left join h.friends as f with f.nickName like 'bubba' where h.description = 'father'" )
|
||||||
|
.list();
|
||||||
|
assertEquals( "subquery rewriting of join table did not take effect", 1, list.size() );
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
data.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
private class TestData {
|
private class TestData {
|
||||||
public void prepare() {
|
public void prepare() {
|
||||||
Session session = openSession();
|
Session session = openSession();
|
||||||
|
@ -202,6 +223,10 @@ public class WithClauseTest extends BaseCoreFunctionalTestCase {
|
||||||
friend.setBodyWeight( 20 );
|
friend.setBodyWeight( 20 );
|
||||||
friend.setDescription( "friend" );
|
friend.setDescription( "friend" );
|
||||||
|
|
||||||
|
Human friend2 = new Human();
|
||||||
|
friend2.setBodyWeight( 20 );
|
||||||
|
friend2.setDescription( "friend2" );
|
||||||
|
|
||||||
child1.setMother( mother );
|
child1.setMother( mother );
|
||||||
child1.setFather( father );
|
child1.setFather( father );
|
||||||
mother.addOffspring( child1 );
|
mother.addOffspring( child1 );
|
||||||
|
@ -214,12 +239,14 @@ public class WithClauseTest extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
father.setFriends( new ArrayList() );
|
father.setFriends( new ArrayList() );
|
||||||
father.getFriends().add( friend );
|
father.getFriends().add( friend );
|
||||||
|
father.getFriends().add( friend2 );
|
||||||
|
|
||||||
session.save( mother );
|
session.save( mother );
|
||||||
session.save( father );
|
session.save( father );
|
||||||
session.save( child1 );
|
session.save( child1 );
|
||||||
session.save( child2 );
|
session.save( child2 );
|
||||||
session.save( friend );
|
session.save( friend );
|
||||||
|
session.save( friend2 );
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
session.close();
|
session.close();
|
||||||
|
|
Loading…
Reference in New Issue