HHH-8637 - Downcasting with TREAT operator should also filter results by the specified Type

This commit is contained in:
Steve Ebersole 2013-11-08 12:35:14 -06:00
parent 246ce294a8
commit 9938937fe7
25 changed files with 1072 additions and 166 deletions

View File

@ -217,6 +217,9 @@ tokens
protected String unquote(String text) {
return text.substring( 1, text.length() - 1 );
}
protected void registerTreat(AST pathToTreat, AST treatAs) {
}
}
statement
@ -369,7 +372,9 @@ joinPath
* Uses a validating semantic predicate to make sure the text of the matched first IDENT is the TREAT keyword
*/
castedJoinPath
: i:IDENT! OPEN! p:path AS! path! CLOSE! {i.getText().equalsIgnoreCase("treat") }?
: i:IDENT! OPEN! p:path AS! a:path! CLOSE! {i.getText().equalsIgnoreCase("treat") }? {
registerTreat( #p, #a );
}
;
withClause
@ -738,7 +743,9 @@ identPrimaryBase
;
castedIdentPrimaryBase
: i:IDENT! OPEN! p:path AS! path! CLOSE! { i.getText().equals("treat") }?
: i:IDENT! OPEN! p:path AS! a:path! CLOSE! { i.getText().equals("treat") }? {
registerTreat( #p, #a );
}
;
aggregate

View File

@ -25,8 +25,10 @@ package org.hibernate.engine.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -88,6 +90,21 @@ public class JoinSequence {
return fromPart;
}
private Set<String> treatAsDeclarations;
public void applyTreatAsDeclarations(Set<String> treatAsDeclarations) {
if ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) {
return;
}
if ( this.treatAsDeclarations == null ) {
this.treatAsDeclarations = new HashSet<String>();
}
this.treatAsDeclarations.addAll( treatAsDeclarations );
}
/**
* Create a full, although shallow, copy.
*
@ -142,23 +159,21 @@ public class JoinSequence {
* Generate a JoinFragment
*
* @param enabledFilters The filters associated with the originating session to properly define join conditions
* @param includeExtraJoins Should {@link #addExtraJoins} to called. Honestly I do not understand the full
* ramifications of this argument
* @param includeAllSubclassJoins Should all subclass joins be added to the rendered JoinFragment?
*
* @return The JoinFragment
*
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
*/
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeExtraJoins) throws MappingException {
return toJoinFragment( enabledFilters, includeExtraJoins, null, null );
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeAllSubclassJoins) throws MappingException {
return toJoinFragment( enabledFilters, includeAllSubclassJoins, null, null );
}
/**
* Generate a JoinFragment
*
* @param enabledFilters The filters associated with the originating session to properly define join conditions
* @param includeExtraJoins Should {@link #addExtraJoins} to called. Honestly I do not understand the full
* ramifications of this argument
* @param includeAllSubclassJoins Should all subclass joins be added to the rendered JoinFragment?
* @param withClauseFragment The with clause (which represents additional join restrictions) fragment
* @param withClauseJoinAlias The
*
@ -168,27 +183,29 @@ public class JoinSequence {
*/
public JoinFragment toJoinFragment(
Map enabledFilters,
boolean includeExtraJoins,
boolean includeAllSubclassJoins,
String withClauseFragment,
String withClauseJoinAlias) throws MappingException {
final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
if ( rootJoinable != null ) {
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters );
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters, treatAsDeclarations );
// JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it
// can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track
// of that fact.
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
if ( includeExtraJoins ) {
//TODO: not quite sure about the full implications of this!
addExtraJoins( joinFragment, rootAlias, rootJoinable, true );
}
addSubclassJoins( joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins, treatAsDeclarations );
}
Joinable last = rootJoinable;
for ( Join join : joins ) {
final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters );
// 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.
// However, it is generally ok given how the HQL parser builds these JoinSequences (a HQL join
// results in a JoinSequence with an empty rootJoinable and a single Join). So we use that here
// as an assumption
final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters, treatAsDeclarations );
String condition;
if ( last != null
&& isManyToManyRoot( last )
@ -221,20 +238,21 @@ public class JoinSequence {
condition
);
//TODO: not quite sure about the full implications of this!
if ( includeExtraJoins ) {
addExtraJoins(
joinFragment,
join.getAlias(),
join.getJoinable(),
join.joinType == JoinType.INNER_JOIN
);
}
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
);
last = join.getJoinable();
}
if ( next != null ) {
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeExtraJoins ) );
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeAllSubclassJoins ) );
}
joinFragment.addCondition( conditions.toString() );
@ -254,11 +272,17 @@ public class JoinSequence {
return false;
}
private void addExtraJoins(JoinFragment joinFragment, String alias, Joinable joinable, boolean innerJoin) {
final boolean include = isIncluded( alias );
private void addSubclassJoins(
JoinFragment joinFragment,
String alias,
Joinable joinable,
boolean innerJoin,
boolean includeSubclassJoins,
Set<String> treatAsDeclarations) {
final boolean include = includeSubclassJoins && isIncluded( alias );
joinFragment.addJoins(
joinable.fromJoinFragment( alias, innerJoin, include ),
joinable.whereJoinFragment( alias, innerJoin, include )
joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations ),
joinable.whereJoinFragment( alias, innerJoin, include, treatAsDeclarations )
);
}

View File

@ -67,12 +67,21 @@ public class EntityGraphQueryHint {
}
}
return getFromElements( originEntityGraph.getAttributeNodes(), fromClause.getFromElement(), fromClause,
walker, explicitFetches );
return getFromElements(
originEntityGraph.getAttributeNodes(),
fromClause.getFromElement(),
fromClause,
walker,
explicitFetches
);
}
private List<FromElement> getFromElements(List attributeNodes, FromElement origin, FromClause fromClause,
HqlSqlWalker walker, Map<String, FromElement> explicitFetches) {
private List<FromElement> getFromElements(
List attributeNodes,
FromElement origin,
FromClause fromClause,
HqlSqlWalker walker,
Map<String, FromElement> explicitFetches) {
final List<FromElement> fromElements = new ArrayList<FromElement>();
for (Object obj : attributeNodes) {
@ -102,8 +111,16 @@ public class EntityGraphQueryHint {
attributeName, classAlias, columns, false);
final JoinSequence joinSequence = walker.getSessionFactoryHelper().createJoinSequence(
false, entityType, tableAlias, JoinType.LEFT_OUTER_JOIN, columns );
fromElement = fromElementFactory.createEntityJoin( entityType.getAssociatedEntityName(), tableAlias,
joinSequence, true, walker.isInFrom(), entityType, role );
fromElement = fromElementFactory.createEntityJoin(
entityType.getAssociatedEntityName(),
tableAlias,
joinSequence,
true,
walker.isInFrom(),
entityType,
role,
null
);
}
else if ( propertyType.isCollectionType() ) {
final String[] columns = origin.toColumns( originTableAlias, attributeName, false );

View File

@ -27,6 +27,11 @@ package org.hibernate.hql.internal.ast;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import antlr.ASTPair;
import antlr.MismatchedTokenException;
@ -398,6 +403,41 @@ public final class HqlParser extends HqlBaseParser {
elementsNode.addChild( p );
}
private Map<String,Set<String>> treatMap;
@Override
protected void registerTreat(AST pathToTreat, AST treatAs) {
final String path = toPathText( pathToTreat );
final String subclassName = toPathText( treatAs );
LOG.debugf( "Registering discovered request to treat(%s as %s)", path, subclassName );
if ( treatMap == null ) {
treatMap = new HashMap<String, Set<String>>();
}
Set<String> subclassNames = treatMap.get( path );
if ( subclassNames == null ) {
subclassNames = new HashSet<String>();
treatMap.put( path, subclassNames );
}
subclassNames.add( subclassName );
}
private String toPathText(AST node) {
final String text = node.getText();
if ( text.equals( "." )
&& node.getFirstChild() != null
&& node.getFirstChild().getNextSibling() != null
&& node.getFirstChild().getNextSibling().getNextSibling() == null ) {
return toPathText( node.getFirstChild() ) + '.' + toPathText( node.getFirstChild().getNextSibling() );
}
return text;
}
public Map<String, Set<String>> getTreatMap() {
return treatMap == null ? Collections.<String, Set<String>>emptyMap() : treatMap;
}
static public void panic() {
//overriden to avoid System.exit
throw new QueryException("Parser: panic");

View File

@ -1280,6 +1280,10 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par
}
}
public Set<String> getTreatAsDeclarationsByPath(String path) {
return hqlParser.getTreatMap().get( path );
}
public static void panic() {
throw new QueryException( "TreeWalker: panic" );
}

View File

@ -24,6 +24,8 @@
*/
package org.hibernate.hql.internal.ast.tree;
import java.util.Set;
import antlr.SemanticException;
import antlr.collections.AST;
import org.jboss.logging.Logger;
@ -490,7 +492,8 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
fetch,
getWalker().isInFrom(),
propertyType,
role
role,
joinPath
);
}
else {

View File

@ -27,6 +27,7 @@ package org.hibernate.hql.internal.ast.tree;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.jboss.logging.Logger;
@ -109,11 +110,13 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
this.classAlias = alias;
this.tableAlias = origin.getTableAlias();
super.initialize( fromClause.getWalker() );
}
protected void initializeComponentJoin(FromElementType elementType) {
this.elementType = elementType;
fromClause.registerFromElement( this );
elementType.applyTreatAsDeclarations( getWalker().getTreatAsDeclarationsByPath( classAlias ) );
this.elementType = elementType;
initialized = true;
}
@ -368,8 +371,13 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
public void setRole(String role) {
this.role = role;
applyTreatAsDeclarations( getWalker().getTreatAsDeclarationsByPath( role ) );
}
public void applyTreatAsDeclarations(Set<String> treatAsDeclarationsByPath) {
elementType.applyTreatAsDeclarations( treatAsDeclarationsByPath );
}
public String getRole() {
return role;
}

View File

@ -249,9 +249,15 @@ public class FromElementFactory implements SqlTokenTypes {
boolean fetchFlag,
boolean inFrom,
EntityType type,
String role) throws SemanticException {
String role,
String joinPath) throws SemanticException {
FromElement elem = createJoin( entityClass, tableAlias, joinSequence, type, false );
elem.setFetch( fetchFlag );
if ( joinPath != null ) {
elem.applyTreatAsDeclarations( fromClause.getWalker().getTreatAsDeclarationsByPath( joinPath ) );
}
EntityPersister entityPersister = elem.getEntityPersister();
int numberOfTables = entityPersister.getQuerySpaces().length;
if ( numberOfTables > 1 && implied && !elem.useFromFragment() ) {
@ -287,6 +293,7 @@ public class FromElementFactory implements SqlTokenTypes {
}
public FromElement createComponentJoin(ComponentType type) {
// need to create a "place holder" from-element that can store the component/alias for this
// component join
return new ComponentJoin( fromClause, origin, classAlias, path, type );

View File

@ -24,8 +24,12 @@
*/
package org.hibernate.hql.internal.ast.tree;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import antlr.SemanticException;
import org.jboss.logging.Logger;
@ -256,8 +260,10 @@ class FromElementType {
return fragment.trim();
}
public void setJoinSequence(JoinSequence joinSequence) {
this.joinSequence = joinSequence;
joinSequence.applyTreatAsDeclarations( treatAsDeclarations );
}
public JoinSequence getJoinSequence() {
@ -268,10 +274,37 @@ class FromElementType {
// Class names in the FROM clause result in a JoinSequence (the old FromParser does this).
if ( persister instanceof Joinable ) {
Joinable joinable = ( Joinable ) persister;
return fromElement.getSessionFactoryHelper().createJoinSequence().setRoot( joinable, getTableAlias() );
final JoinSequence joinSequence = fromElement.getSessionFactoryHelper().createJoinSequence().setRoot( joinable, getTableAlias() );
joinSequence.applyTreatAsDeclarations( treatAsDeclarations );
return joinSequence;
}
else {
return null; // TODO: Should this really return null? If not, figure out something better to do here.
// TODO: Should this really return null? If not, figure out something better to do here.
return null;
}
}
private Set<String> treatAsDeclarations;
public void applyTreatAsDeclarations(Set<String> treatAsDeclarations) {
if ( treatAsDeclarations != null && !treatAsDeclarations.isEmpty() ) {
if ( this.treatAsDeclarations == null ) {
this.treatAsDeclarations = new HashSet<String>();
}
for ( String treatAsSubclassName : treatAsDeclarations ) {
try {
EntityPersister subclassPersister = fromElement.getSessionFactoryHelper().requireClassPersister( treatAsSubclassName );
this.treatAsDeclarations.add( subclassPersister.getEntityName() );
}
catch (SemanticException e) {
throw new QueryException( "Unable to locate persister for subclass named in TREAT-AS : " + treatAsSubclassName );
}
}
if ( joinSequence != null ) {
joinSequence.applyTreatAsDeclarations( this.treatAsDeclarations );
}
}
}

View File

@ -149,12 +149,16 @@ public class IdentNode extends FromReferenceNode implements SelectExpression {
}
private boolean resolveAsAlias() {
final String alias = getText();
// This is not actually a constant, but a reference to FROM element.
final FromElement element = getWalker().getCurrentFromClause().getFromElement( getText() );
final FromElement element = getWalker().getCurrentFromClause().getFromElement( alias );
if ( element == null ) {
return false;
}
element.applyTreatAsDeclarations( getWalker().getTreatAsDeclarationsByPath( alias ) );
setType( SqlTokenTypes.ALIAS_REF );
setFromElement( element );

View File

@ -408,6 +408,16 @@ public final class ArrayHelper {
return i;
}
public static String[] reverse(String[] source) {
final int length = source.length;
final String[] destination = new String[length];
for ( int i = 0; i < length; i++ ) {
final int x = length - i - 1;
destination[x] = source[i];
}
return destination;
}
public static void main(String... args) {
int[] batchSizes = ArrayHelper.getBatchSizes( 32 );

View File

@ -31,6 +31,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
@ -1671,18 +1672,38 @@ public abstract class AbstractCollectionPersister
return hasWhere() ? " and " + getSQLWhereString( alias ) : "";
}
public String filterFragment(String alias, Map enabledFilters) throws MappingException {
protected String filterFragment(String alias, Set<String> treatAsDeclarations) throws MappingException {
return hasWhere() ? " and " + getSQLWhereString( alias ) : "";
}
public String filterFragment(String alias, Map enabledFilters) throws MappingException {
StringBuilder sessionFilterFragment = new StringBuilder();
filterHelper.render( sessionFilterFragment, getFilterAliasGenerator(alias), enabledFilters );
return sessionFilterFragment.append( filterFragment( alias ) ).toString();
}
@Override
public String filterFragment(
String alias,
Map enabledFilters,
Set<String> treatAsDeclarations) {
StringBuilder sessionFilterFragment = new StringBuilder();
filterHelper.render( sessionFilterFragment, getFilterAliasGenerator(alias), enabledFilters );
return sessionFilterFragment.append( filterFragment( alias, treatAsDeclarations ) ).toString();
}
@Override
public String oneToManyFilterFragment(String alias) throws MappingException {
return "";
}
@Override
public String oneToManyFilterFragment(String alias, Set<String> treatAsDeclarations) {
return oneToManyFilterFragment( alias );
}
protected boolean isInsertCallable() {
return insertCallable;
}

View File

@ -27,6 +27,7 @@ import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
@ -343,14 +344,26 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
.createBatchingCollectionInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
}
@Override
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return "";
}
@Override
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses, Set<String> treatAsDeclarations) {
return "";
}
@Override
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return "";
}
@Override
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses, Set<String> treatAsDeclarations) {
return "";
}
@Override
protected CollectionInitializer createSubselectInitializer(SubselectFetch subselect, SessionImplementor session) {
return new SubselectCollectionLoader(

View File

@ -28,6 +28,7 @@ import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
@ -478,21 +479,37 @@ public class OneToManyPersister extends AbstractCollectionPersister {
.createBatchingOneToManyInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
}
public String fromJoinFragment(String alias,
boolean innerJoin,
boolean includeSubclasses) {
return ( ( Joinable ) getElementPersister() ).fromJoinFragment( alias, innerJoin, includeSubclasses );
@Override
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return ( (Joinable) getElementPersister() ).fromJoinFragment( alias, innerJoin, includeSubclasses );
}
public String whereJoinFragment(String alias,
boolean innerJoin,
boolean includeSubclasses) {
return ( ( Joinable ) getElementPersister() ).whereJoinFragment( alias, innerJoin, includeSubclasses );
@Override
public String fromJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
return ( (Joinable) getElementPersister() ).fromJoinFragment( alias, innerJoin, includeSubclasses, treatAsDeclarations );
}
@Override
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return ( (Joinable) getElementPersister() ).whereJoinFragment( alias, innerJoin, includeSubclasses );
}
@Override
public String whereJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
return ( (Joinable) getElementPersister() ).whereJoinFragment( alias, innerJoin, includeSubclasses, treatAsDeclarations );
}
@Override
public String getTableName() {
return ( ( Joinable ) getElementPersister() ).getTableName();
return ( (Joinable) getElementPersister() ).getTableName();
}
@Override
@ -505,6 +522,15 @@ public class OneToManyPersister extends AbstractCollectionPersister {
}
@Override
protected String filterFragment(String alias, Set<String> treatAsDeclarations) throws MappingException {
String result = super.filterFragment( alias );
if ( getElementPersister() instanceof Joinable ) {
result += ( ( Joinable ) getElementPersister() ).oneToManyFilterFragment( alias, treatAsDeclarations );
}
return result;
}
@Override
protected CollectionInitializer createSubselectInitializer(SubselectFetch subselect, SessionImplementor session) {
return new SubselectOneToManyLoader(

View File

@ -318,6 +318,8 @@ public abstract class AbstractEntityPersister
protected abstract String filterFragment(String alias) throws MappingException;
protected abstract String filterFragment(String alias, Set<String> treatAsDeclarations);
private static final String DISCRIMINATOR_ALIAS = "clazz_";
public String getDiscriminatorColumnName() {
@ -3697,12 +3699,20 @@ public abstract class AbstractEntityPersister
}
}
@Override
public String filterFragment(String alias, Map enabledFilters) throws MappingException {
final StringBuilder sessionFilterFragment = new StringBuilder();
filterHelper.render( sessionFilterFragment, getFilterAliasGenerator(alias), enabledFilters );
return sessionFilterFragment.append( filterFragment( alias ) ).toString();
}
@Override
public String filterFragment(String alias, Map enabledFilters, Set<String> treatAsDeclarations) {
final StringBuilder sessionFilterFragment = new StringBuilder();
filterHelper.render( sessionFilterFragment, getFilterAliasGenerator(alias), enabledFilters );
return sessionFilterFragment.append( filterFragment( alias, treatAsDeclarations ) ).toString();
}
public String generateFilterConditionAlias(String rootAlias) {
return rootAlias;
}
@ -3711,55 +3721,137 @@ public abstract class AbstractEntityPersister
return "";
}
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return getSubclassTableSpan() == 1 ?
"" : //just a performance opt!
createJoin( alias, innerJoin, includeSubclasses ).toFromFragmentString();
@Override
public String oneToManyFilterFragment(String alias, Set<String> treatAsDeclarations) {
return oneToManyFilterFragment( alias );
}
@Override
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
// NOTE : Not calling createJoin here is just a performance optimization
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, Collections.<String>emptySet() ).toFromFragmentString();
}
@Override
public String fromJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
// NOTE : Not calling createJoin here is just a performance optimization
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations ).toFromFragmentString();
}
@Override
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return getSubclassTableSpan() == 1 ?
"" : //just a performance opt!
createJoin( alias, innerJoin, includeSubclasses ).toWhereFragmentString();
// NOTE : Not calling createJoin here is just a performance optimization
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, Collections.<String>emptySet() ).toWhereFragmentString();
}
@Override
public String whereJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
// NOTE : Not calling createJoin here is just a performance optimization
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations ).toWhereFragmentString();
}
protected boolean isSubclassTableLazy(int j) {
return false;
}
protected JoinFragment createJoin(String name, boolean innerJoin, boolean includeSubclasses) {
final String[] idCols = StringHelper.qualify( name, getIdentifierColumnNames() ); //all joins join to the pk of the driving table
protected JoinFragment createJoin(String name, boolean innerJoin, boolean includeSubclasses, Set<String> treatAsDeclarations) {
// IMPL NOTE : all joins join to the pk of the driving table
final String[] idCols = StringHelper.qualify( name, getIdentifierColumnNames() );
final JoinFragment join = getFactory().getDialect().createOuterJoinFragment();
final int tableSpan = getSubclassTableSpan();
for ( int j = 1; j < tableSpan; j++ ) { //notice that we skip the first table; it is the driving table!
final boolean joinIsIncluded = isClassOrSuperclassTable( j ) ||
( includeSubclasses && !isSubclassTableSequentialSelect( j ) && !isSubclassTableLazy( j ) );
if ( joinIsIncluded ) {
join.addJoin( getSubclassTableName( j ),
// IMPL NOTE : notice that we skip the first table; it is the driving table!
for ( int j = 1; j < tableSpan; j++ ) {
final JoinType joinType = determineSubclassTableJoinType(
j,
innerJoin,
includeSubclasses,
treatAsDeclarations
);
if ( joinType != null && joinType != JoinType.NONE ) {
join.addJoin(
getSubclassTableName( j ),
generateTableAlias( name, j ),
idCols,
getSubclassTableKeyColumns( j ),
innerJoin && isClassOrSuperclassTable( j ) && !isInverseTable( j ) && !isNullableTable( j ) ?
JoinType.INNER_JOIN : //we can inner join to superclass tables (the row MUST be there)
JoinType.LEFT_OUTER_JOIN //we can never inner join to subclass tables
);
joinType
);
}
}
return join;
}
protected JoinType determineSubclassTableJoinType(
int subclassTableNumber,
boolean canInnerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
if ( isClassOrSuperclassTable( subclassTableNumber ) ) {
final boolean shouldInnerJoin = canInnerJoin
&& !isInverseTable( subclassTableNumber )
&& !isNullableTable( subclassTableNumber );
// the table is either this persister's driving table or (one of) its super class persister's driving
// tables which can be inner joined as long as the `shouldInnerJoin` condition resolves to true
return shouldInnerJoin ? JoinType.INNER_JOIN : JoinType.LEFT_OUTER_JOIN;
}
// otherwise we have a subclass table and need to look a little deeper...
// IMPL NOTE : By default includeSubclasses indicates that all subclasses should be joined and that each
// subclass ought to be joined by outer-join. However, TREAT-AS always requires that an inner-join be used
// so we give TREAT-AS higher precedence...
if ( isSubclassTableIndicatedByTreatAsDeclarations( subclassTableNumber, treatAsDeclarations ) ) {
return JoinType.INNER_JOIN;
}
if ( includeSubclasses
&& !isSubclassTableSequentialSelect( subclassTableNumber )
&& !isSubclassTableLazy( subclassTableNumber ) ) {
return JoinType.LEFT_OUTER_JOIN;
}
return JoinType.NONE;
}
protected boolean isSubclassTableIndicatedByTreatAsDeclarations(
int subclassTableNumber,
Set<String> treatAsDeclarations) {
return false;
}
protected JoinFragment createJoin(int[] tableNumbers, String drivingAlias) {
final String[] keyCols = StringHelper.qualify( drivingAlias, getSubclassTableKeyColumns( tableNumbers[0] ) );
final JoinFragment jf = getFactory().getDialect().createOuterJoinFragment();
for ( int i = 1; i < tableNumbers.length; i++ ) { //skip the driving table
// IMPL NOTE : notice that we skip the first table; it is the driving table!
for ( int i = 1; i < tableNumbers.length; i++ ) {
final int j = tableNumbers[i];
jf.addJoin( getSubclassTableName( j ),
generateTableAlias( getRootAlias(), j ),
keyCols,
getSubclassTableKeyColumns( j ),
isInverseSubclassTable( j ) || isNullableSubclassTable( j ) ?
JoinType.LEFT_OUTER_JOIN :
JoinType.INNER_JOIN );
isInverseSubclassTable( j ) || isNullableSubclassTable( j )
? JoinType.LEFT_OUTER_JOIN
: JoinType.INNER_JOIN
);
}
return jf;
}

View File

@ -24,6 +24,7 @@
*/
package org.hibernate.persister.entity;
import java.util.Map;
import java.util.Set;
import org.hibernate.MappingException;
@ -55,21 +56,44 @@ public interface Joinable {
* (optional operation)
*/
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses);
/**
* Get the where clause part of any joins
* (optional operation)
*/
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses, Set<String> treatAsDeclarations);
/**
* Get the from clause part of any joins
* (optional operation)
*/
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses);
/**
* Get the from clause part of any joins
* (optional operation)
*/
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses, Set<String> treatAsDeclarations);
/**
* The columns to join on
*/
public String[] getKeyColumnNames();
/**
* Get the where clause filter, given a query alias and considering enabled session filters
*/
public String filterFragment(String alias, Map enabledFilters) throws MappingException;
/**
* Get the where clause filter, given a query alias and considering enabled session filters
*/
public String filterFragment(String alias, Map enabledFilters, Set<String> treatAsDeclarations) throws MappingException;
public String oneToManyFilterFragment(String alias) throws MappingException;
public String oneToManyFilterFragment(String alias, Set<String> treatAsDeclarations);
/**
* Is this instance actually a CollectionPersister?
*/

View File

@ -24,10 +24,14 @@
package org.hibernate.persister.entity;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
@ -45,6 +49,7 @@ import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
@ -498,6 +503,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
k++;
}
subclassNamesBySubclassTable = buildSubclassNamesBySubclassTableMapping( persistentClass, factory );
initLockers();
initSubclassPropertyAliasesMap( persistentClass );
@ -506,6 +513,160 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
}
/**
* Used to hold the name of subclasses that each "subclass table" is part of. For example, given a hierarchy like:
* {@code JoinedEntity <- JoinedEntitySubclass <- JoinedEntitySubSubclass}..
* <p/>
* For the persister for JoinedEntity, we'd have:
* <pre>
* subclassClosure[0] = "JoinedEntitySubSubclass"
* subclassClosure[1] = "JoinedEntitySubclass"
* subclassClosure[2] = "JoinedEntity"
*
* subclassTableNameClosure[0] = "T_JoinedEntity"
* subclassTableNameClosure[1] = "T_JoinedEntitySubclass"
* subclassTableNameClosure[2] = "T_JoinedEntitySubSubclass"
*
* subclassNameClosureBySubclassTable[0] = ["JoinedEntitySubSubclass", "JoinedEntitySubclass"]
* subclassNameClosureBySubclassTable[1] = ["JoinedEntitySubSubclass"]
* </pre>
* Note that there are only 2 entries in subclassNameClosureBySubclassTable. That is because there are really only
* 2 tables here that make up the subclass mapping, the others make up the class/superclass table mappings. We
* do not need to account for those here. The "offset" is defined by the value of {@link #getTableSpan()}.
* Therefore the corresponding row in subclassNameClosureBySubclassTable for a given row in subclassTableNameClosure
* is calculated as {@code subclassTableNameClosureIndex - getTableSpan()}.
* <p/>
* As we consider each subclass table we can look into this array based on the subclass table's index and see
* which subclasses would require it to be included. E.g., given {@code TREAT( x AS JoinedEntitySubSubclass )},
* when trying to decide whether to include join to "T_JoinedEntitySubclass" (subclassTableNameClosureIndex = 1),
* we'd look at {@code subclassNameClosureBySubclassTable[0]} and see if the TREAT-AS subclass name is included in
* its values. Since {@code subclassNameClosureBySubclassTable[1]} includes "JoinedEntitySubSubclass", we'd
* consider it included.
* <p/>
* {@link #subclassTableNameClosure} also accounts for secondary tables and we properly handle those as we
* build the subclassNamesBySubclassTable array and they are therefore properly handled when we use it
*/
private final String[][] subclassNamesBySubclassTable;
/**
* Essentially we are building a mapping that we can later use to determine whether a given "subclass table"
* should be included in joins when JPA TREAT-AS is used.
*
* @param persistentClass
* @param factory
* @return
*/
private String[][] buildSubclassNamesBySubclassTableMapping(PersistentClass persistentClass, SessionFactoryImplementor factory) {
// this value represents the number of subclasses (and not the class itself)
final int numberOfSubclassTables = subclassTableNameClosure.length - coreTableSpan;
if ( numberOfSubclassTables == 0 ) {
return new String[0][];
}
final String[][] mapping = new String[numberOfSubclassTables][];
processPersistentClassHierarchy( persistentClass, true, factory, mapping );
return mapping;
}
private Set<String> processPersistentClassHierarchy(
PersistentClass persistentClass,
boolean isBase,
SessionFactoryImplementor factory,
String[][] mapping) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// collect all the class names that indicate that the "main table" of the given PersistentClass should be
// included when one of the collected class names is used in TREAT
final Set<String> classNames = new HashSet<String>();
final Iterator itr = persistentClass.getDirectSubclasses();
while ( itr.hasNext() ) {
final Subclass subclass = (Subclass) itr.next();
final Set<String> subclassSubclassNames = processPersistentClassHierarchy(
subclass,
false,
factory,
mapping
);
classNames.addAll( subclassSubclassNames );
}
classNames.add( persistentClass.getEntityName() );
if ( ! isBase ) {
MappedSuperclass msc = persistentClass.getSuperMappedSuperclass();
while ( msc != null ) {
classNames.add( msc.getMappedClass().getName() );
msc = msc.getSuperMappedSuperclass();
}
associateSubclassNamesToSubclassTableIndexes( persistentClass, classNames, mapping, factory );
}
return classNames;
}
private void associateSubclassNamesToSubclassTableIndexes(
PersistentClass persistentClass,
Set<String> classNames,
String[][] mapping,
SessionFactoryImplementor factory) {
final String tableName = persistentClass.getTable().getQualifiedName(
factory.getDialect(),
factory.getSettings().getDefaultCatalogName(),
factory.getSettings().getDefaultSchemaName()
);
associateSubclassNamesToSubclassTableIndex( tableName, classNames, mapping );
Iterator itr = persistentClass.getJoinIterator();
while ( itr.hasNext() ) {
final Join join = (Join) itr.next();
final String secondaryTableName = join.getTable().getQualifiedName(
factory.getDialect(),
factory.getSettings().getDefaultCatalogName(),
factory.getSettings().getDefaultSchemaName()
);
associateSubclassNamesToSubclassTableIndex( secondaryTableName, classNames, mapping );
}
}
private void associateSubclassNamesToSubclassTableIndex(
String tableName,
Set<String> classNames,
String[][] mapping) {
// find the table's entry in the subclassTableNameClosure array
boolean found = false;
for ( int i = 0; i < subclassTableNameClosure.length; i++ ) {
if ( subclassTableNameClosure[i].equals( tableName ) ) {
found = true;
final int index = i - coreTableSpan;
if ( index < 0 || index >= mapping.length ) {
throw new IllegalStateException(
String.format(
"Encountered 'subclass table index' [%s] was outside expected range ( [%s] < i < [%s] )",
index,
0,
mapping.length
)
);
}
mapping[index] = classNames.toArray( new String[ classNames.size() ] );
break;
}
}
if ( !found ) {
throw new IllegalStateException(
String.format(
"Was unable to locate subclass table [%s] in 'subclassTableNameClosure'",
tableName
)
);
}
}
public JoinedSubclassEntityPersister(
final EntityBinding entityBinding,
final EntityRegionAccessStrategy cacheAccessStrategy,
@ -545,6 +706,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
discriminatorSQLString = null;
coreTableSpan = -1;
isNullableTable = null;
subclassNamesBySubclassTable = null;
}
protected boolean isNullableTable(int j) {
@ -716,10 +878,16 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
return cases;
}
@Override
public String filterFragment(String alias) {
return hasWhere() ?
" and " + getSQLWhereString( generateFilterConditionAlias( alias ) ) :
"";
return hasWhere()
? " and " + getSQLWhereString( generateFilterConditionAlias( alias ) )
: "";
}
@Override
public String filterFragment(String alias, Set<String> treatAsDeclarations) {
return filterFragment( alias );
}
public String generateFilterConditionAlias(String rootAlias) {
@ -802,6 +970,41 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
return isClassOrSuperclassTable[j];
}
@Override
protected boolean isSubclassTableIndicatedByTreatAsDeclarations(
int subclassTableNumber,
Set<String> treatAsDeclarations) {
if ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) {
return false;
}
final String[] inclusionSubclassNameClosure = getSubclassNameClosureBySubclassTable( subclassTableNumber );
// NOTE : we assume the entire hierarchy is joined-subclass here
for ( String subclassName : treatAsDeclarations ) {
for ( String inclusionSubclassName : inclusionSubclassNameClosure ) {
if ( inclusionSubclassName.equals( subclassName ) ) {
return true;
}
}
}
return false;
}
private String[] getSubclassNameClosureBySubclassTable(int subclassTableNumber) {
final int index = subclassTableNumber - getTableSpan();
if ( index > subclassNamesBySubclassTable.length ) {
throw new IllegalArgumentException(
"Given subclass table number is outside expected range [" + subclassNamesBySubclassTable.length
+ "] as defined by subclassTableNameClosure/subclassClosure"
);
}
return subclassNamesBySubclassTable[index];
}
public String getPropertyTableName(String propertyName) {
Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName );
if ( index == null ) {

View File

@ -28,7 +28,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
@ -835,50 +837,97 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
return getTableName() + ' ' + name;
}
@Override
public String filterFragment(String alias) throws MappingException {
String result = discriminatorFilterFragment(alias);
if ( hasWhere() ) result += " and " + getSQLWhereString(alias);
return result;
}
public String oneToManyFilterFragment(String alias) throws MappingException {
return forceDiscriminator ?
discriminatorFilterFragment(alias) :
"";
}
private String discriminatorFilterFragment(String alias) throws MappingException {
if ( needsDiscriminator() ) {
InFragment frag = new InFragment();
return discriminatorFilterFragment( alias, null );
}
public String oneToManyFilterFragment(String alias) throws MappingException {
return forceDiscriminator
? discriminatorFilterFragment( alias, null )
: "";
}
if ( isDiscriminatorFormula() ) {
frag.setFormula( alias, getDiscriminatorFormulaTemplate() );
}
else {
frag.setColumn( alias, getDiscriminatorColumnName() );
}
@Override
public String oneToManyFilterFragment(String alias, Set<String> treatAsDeclarations) {
return needsDiscriminator()
? discriminatorFilterFragment( alias, treatAsDeclarations )
: "";
}
String[] subclasses = getSubclassClosure();
for ( int i=0; i<subclasses.length; i++ ) {
final Queryable queryable = (Queryable) getFactory().getEntityPersister( subclasses[i] );
if ( !queryable.isAbstract() ) frag.addValue( queryable.getDiscriminatorSQLValue() );
}
StringBuilder buf = new StringBuilder(50)
.append(" and ")
.append( frag.toFragmentString() );
return buf.toString();
@Override
public String filterFragment(String alias, Set<String> treatAsDeclarations) {
String result = discriminatorFilterFragment( alias, treatAsDeclarations );
if ( hasWhere() ) {
result += " and " + getSQLWhereString( alias );
}
else {
return result;
}
private String discriminatorFilterFragment(String alias, Set<String> treatAsDeclarations) {
final boolean hasTreatAs = treatAsDeclarations != null && !treatAsDeclarations.isEmpty();
if ( !needsDiscriminator() && !hasTreatAs) {
return "";
}
final InFragment frag = new InFragment();
if ( isDiscriminatorFormula() ) {
frag.setFormula( alias, getDiscriminatorFormulaTemplate() );
}
else {
frag.setColumn( alias, getDiscriminatorColumnName() );
}
if ( hasTreatAs ) {
frag.addValues( decodeTreatAsRequests( treatAsDeclarations ) );
}
else {
frag.addValues( fullDiscriminatorValues() );
}
return " and " + frag.toFragmentString();
}
private boolean needsDiscriminator() {
return forceDiscriminator || isInherited();
}
private String[] decodeTreatAsRequests(Set<String> treatAsDeclarations) {
final List<String> values = new ArrayList<String>();
for ( String subclass : treatAsDeclarations ) {
final Queryable queryable = (Queryable) getFactory().getEntityPersister( subclass );
if ( !queryable.isAbstract() ) {
values.add( queryable.getDiscriminatorSQLValue() );
}
}
return values.toArray( new String[ values.size() ] );
}
private String[] fullDiscriminatorValues;
private String[] fullDiscriminatorValues() {
if ( fullDiscriminatorValues == null ) {
// first access; build it
final List<String> values = new ArrayList<String>();
for ( String subclass : getSubclassClosure() ) {
final Queryable queryable = (Queryable) getFactory().getEntityPersister( subclass );
if ( !queryable.isAbstract() ) {
values.add( queryable.getDiscriminatorSQLValue() );
}
}
fullDiscriminatorValues = values.toArray( new String[values.size() ] );
}
return fullDiscriminatorValues;
}
public String getSubclassPropertyTableName(int i) {
return subclassTableNameClosure[ subclassPropertyTableNumberClosure[i] ];
}

View File

@ -30,6 +30,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
@ -356,10 +357,16 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
return getTableName() + ' ' + name;
}
@Override
public String filterFragment(String name) {
return hasWhere() ?
" and " + getSQLWhereString(name) :
"";
return hasWhere()
? " and " + getSQLWhereString( name )
: "";
}
@Override
protected String filterFragment(String alias, Set<String> treatAsDeclarations) {
return filterFragment( alias );
}
public String getSubclassPropertyTableName(int i) {

View File

@ -25,6 +25,7 @@
package org.hibernate.sql;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.internal.util.StringHelper;
@ -34,6 +35,7 @@ import org.hibernate.internal.util.StringHelper;
* <br>
* <code>... in(...)</code>
* <br>
*
* @author Gavin King
*/
public class InFragment {
@ -50,7 +52,12 @@ public class InFragment {
* @return {@code this}, for method chaining
*/
public InFragment addValue(Object value) {
values.add(value);
values.add( value );
return this;
}
public InFragment addValues(Object[] values) {
Collections.addAll( this.values, values );
return this;
}
@ -61,72 +68,76 @@ public class InFragment {
public InFragment setColumn(String alias, String columnName) {
this.columnName = StringHelper.qualify( alias, columnName );
return setColumn(this.columnName);
return setColumn( this.columnName );
}
public InFragment setFormula(String alias, String formulaTemplate) {
this.columnName = StringHelper.replace(formulaTemplate, Template.TEMPLATE, alias);
return setColumn(this.columnName);
this.columnName = StringHelper.replace( formulaTemplate, Template.TEMPLATE, alias );
return setColumn( this.columnName );
}
public String toFragmentString() {
if ( values.size() == 0 ) {
return "1=2";
}
if (values.size() == 0) {
return "1=2";
}
StringBuilder buf = new StringBuilder( values.size() * 5 );
StringBuilder buf = new StringBuilder(values.size() * 5);
if ( values.size() == 1 ) {
Object value = values.get( 0 );
buf.append( columnName );
if (values.size() == 1) {
Object value = values.get(0);
buf.append(columnName);
if ( NULL.equals( value ) ) {
buf.append( " is null" );
}
else {
if ( NOT_NULL.equals( value ) ) {
buf.append( " is not null" );
}
else {
buf.append( '=' ).append( value );
}
}
return buf.toString();
}
if (NULL.equals(value)) {
buf.append(" is null");
} else {
if (NOT_NULL.equals(value)) {
buf.append(" is not null");
} else {
buf.append('=').append(value);
}
}
return buf.toString();
}
boolean allowNull = false;
boolean allowNull = false;
for (Object value : values) {
if (NULL.equals(value)) {
allowNull = true;
} else {
if (NOT_NULL.equals(value)) {
throw new IllegalArgumentException("not null makes no sense for in expression");
}
}
}
for ( Object value : values ) {
if ( NULL.equals( value ) ) {
allowNull = true;
}
else {
if ( NOT_NULL.equals( value ) ) {
throw new IllegalArgumentException( "not null makes no sense for in expression" );
}
}
}
if (allowNull) {
buf.append('(').append(columnName).append(" is null or ").append(columnName).append(" in (");
} else {
buf.append(columnName).append(" in (");
}
if ( allowNull ) {
buf.append( '(' ).append( columnName ).append( " is null or " ).append( columnName ).append( " in (" );
}
else {
buf.append( columnName ).append( " in (" );
}
for (Object value : values) {
if ( ! NULL.equals(value) ) {
buf.append(value);
buf.append(", ");
}
}
for ( Object value : values ) {
if ( !NULL.equals( value ) ) {
buf.append( value );
buf.append( ", " );
}
}
buf.setLength(buf.length() - 2);
buf.setLength( buf.length() - 2 );
if (allowNull) {
buf.append("))");
} else {
buf.append(')');
}
if ( allowNull ) {
buf.append( "))" );
}
else {
buf.append( ')' );
}
return buf.toString();
return buf.toString();
}
}

View File

@ -30,6 +30,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.dom4j.Node;
@ -510,6 +511,14 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT
throw new UnsupportedOperationException();
}
@Override
public String getOnCondition(
String alias,
SessionFactoryImplementor factory,
Map enabledFilters,
Set<String> treatAsDeclarations) {
throw new UnsupportedOperationException();
}
/**
* Used to externalize discrimination per a given identifier. For example, when writing to

View File

@ -24,6 +24,7 @@
package org.hibernate.type;
import java.util.Map;
import java.util.Set;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -79,6 +80,12 @@ public interface AssociationType extends Type {
*/
public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters)
throws MappingException;
/**
* Get the "filtering" SQL fragment that is applied in the
* SQL on clause, in addition to the usual join condition
*/
public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters, Set<String> treatAsDeclarations);
/**
* Do we dirty check this association, even when there are

View File

@ -33,6 +33,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
@ -694,6 +695,15 @@ public abstract class CollectionType extends AbstractType implements Association
return getAssociatedJoinable( factory ).filterFragment( alias, enabledFilters );
}
@Override
public String getOnCondition(
String alias,
SessionFactoryImplementor factory,
Map enabledFilters,
Set<String> treatAsDeclarations) {
return getAssociatedJoinable( factory ).filterFragment( alias, enabledFilters, treatAsDeclarations );
}
/**
* instantiate a collection wrapper (called when loading an object)
*

View File

@ -27,6 +27,7 @@ import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import org.dom4j.Element;
import org.dom4j.Node;
@ -466,13 +467,22 @@ public abstract class EntityType extends AbstractType implements AssociationType
}
}
public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters)
throws MappingException {
if ( isReferenceToPrimaryKey() ) { //TODO: this is a bit arbitrary, expose a switch to the user?
@Override
public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters) {
return getOnCondition( alias, factory, enabledFilters, null );
}
@Override
public String getOnCondition(
String alias,
SessionFactoryImplementor factory,
Map enabledFilters,
Set<String> treatAsDeclarations) {
if ( isReferenceToPrimaryKey() && ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) ) {
return "";
}
else {
return getAssociatedJoinable( factory ).filterFragment( alias, enabledFilters );
return getAssociatedJoinable( factory ).filterFragment( alias, enabledFilters, treatAsDeclarations );
}
}

View File

@ -23,21 +23,288 @@
*/
package org.hibernate.test.jpa.ql;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.util.List;
import org.hibernate.Session;
import org.junit.Test;
import org.hibernate.test.jpa.AbstractJPATest;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
/**
* @author Steve Ebersole
*/
public class TreatKeywordTest extends AbstractJPATest {
public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
JoinedEntity.class, JoinedEntitySubclass.class, JoinedEntitySubSubclass.class,
JoinedEntitySubclass2.class, JoinedEntitySubSubclass2.class,
DiscriminatorEntity.class, DiscriminatorEntitySubclass.class, DiscriminatorEntitySubSubclass.class
};
}
@Test
public void testUsageInSelect() {
public void testBasicUsageInJoin() {
// todo : assert invalid naming of non-subclasses in TREAT statement
Session s = openSession();
s.createQuery( "from MyEntity e join treat(e.other as MySubclassEntity) o" ).list();
s.createQuery( "from MyEntity e join TREAT(e.other as MySubclassEntity) o" ).list();
s.createQuery( "from DiscriminatorEntity e join treat(e.other as DiscriminatorEntitySubclass) o" ).list();
s.createQuery( "from DiscriminatorEntity e join treat(e.other as DiscriminatorEntitySubSubclass) o" ).list();
s.createQuery( "from DiscriminatorEntitySubclass e join treat(e.other as DiscriminatorEntitySubSubclass) o" ).list();
s.createQuery( "from JoinedEntity e join treat(e.other as JoinedEntitySubclass) o" ).list();
s.createQuery( "from JoinedEntity e join treat(e.other as JoinedEntitySubSubclass) o" ).list();
s.createQuery( "from JoinedEntitySubclass e join treat(e.other as JoinedEntitySubSubclass) o" ).list();
s.close();
}
@Test
@TestForIssue( jiraKey = "HHH-8637" )
public void testFilteringDiscriminatorSubclasses() {
Session s = openSession();
s.beginTransaction();
DiscriminatorEntity root = new DiscriminatorEntity( 1, "root" );
s.save( root );
DiscriminatorEntitySubclass child = new DiscriminatorEntitySubclass( 2, "child", root );
s.save( child );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
// in select clause
List result = s.createQuery( "select e from DiscriminatorEntity e" ).list();
assertEquals( 2, result.size() );
result = s.createQuery( "select treat (e as DiscriminatorEntitySubclass) from DiscriminatorEntity e" ).list();
assertEquals( 1, result.size() );
result = s.createQuery( "select treat (e as DiscriminatorEntitySubSubclass) from DiscriminatorEntity e" ).list();
assertEquals( 0, result.size() );
// in join
result = s.createQuery( "from DiscriminatorEntity e inner join e.other" ).list();
assertEquals( 1, result.size() );
result = s.createQuery( "from DiscriminatorEntity e inner join treat (e.other as DiscriminatorEntitySubclass)" ).list();
assertEquals( 0, result.size() );
result = s.createQuery( "from DiscriminatorEntity e inner join treat (e.other as DiscriminatorEntitySubSubclass)" ).list();
assertEquals( 0, result.size() );
s.close();
s = openSession();
s.beginTransaction();
s.delete( root );
s.delete( child );
s.getTransaction().commit();
s.close();
}
@Test
@TestForIssue( jiraKey = "HHH-8637" )
public void testFilteringJoinedSubclasses() {
Session s = openSession();
s.beginTransaction();
JoinedEntity root = new JoinedEntity( 1, "root" );
s.save( root );
JoinedEntitySubclass child = new JoinedEntitySubclass( 2, "child", root );
s.save( child );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
// in the select clause which causes an implicit inclusion of subclass joins, the test here makes sure that
// the TREAT-AS effects the join-type used.
List result = s.createQuery( "select e from JoinedEntity e" ).list();
assertEquals( 2, result.size() );
result = s.createQuery( "select treat (e as JoinedEntitySubclass) from JoinedEntity e" ).list();
assertEquals( 1, result.size() );
result = s.createQuery( "select treat (e as JoinedEntitySubSubclass) from JoinedEntity e" ).list();
assertEquals( 0, result.size() );
// in join
result = s.createQuery( "from JoinedEntity e inner join e.other" ).list();
assertEquals( 1, result.size() );
result = s.createQuery( "from JoinedEntity e inner join treat (e.other as JoinedEntitySubclass)" ).list();
assertEquals( 0, result.size() );
result = s.createQuery( "from JoinedEntity e inner join treat (e.other as JoinedEntitySubSubclass)" ).list();
assertEquals( 0, result.size() );
s.close();
s = openSession();
s.beginTransaction();
s.delete( child );
s.delete( root );
s.getTransaction().commit();
s.close();
}
@Entity( name = "JoinedEntity" )
@Table( name = "JoinedEntity" )
@Inheritance( strategy = InheritanceType.JOINED )
public static class JoinedEntity {
@Id
public Integer id;
public String name;
@ManyToOne( fetch = FetchType.LAZY )
public JoinedEntity other;
public JoinedEntity() {
}
public JoinedEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
public JoinedEntity(Integer id, String name, JoinedEntity other) {
this.id = id;
this.name = name;
this.other = other;
}
}
@Entity( name = "JoinedEntitySubclass" )
@Table( name = "JoinedEntitySubclass" )
public static class JoinedEntitySubclass extends JoinedEntity {
public JoinedEntitySubclass() {
}
public JoinedEntitySubclass(Integer id, String name) {
super( id, name );
}
public JoinedEntitySubclass(Integer id, String name, JoinedEntity other) {
super( id, name, other );
}
}
@Entity( name = "JoinedEntitySubSubclass" )
@Table( name = "JoinedEntitySubSubclass" )
public static class JoinedEntitySubSubclass extends JoinedEntitySubclass {
public JoinedEntitySubSubclass() {
}
public JoinedEntitySubSubclass(Integer id, String name) {
super( id, name );
}
public JoinedEntitySubSubclass(Integer id, String name, JoinedEntity other) {
super( id, name, other );
}
}
@Entity( name = "JoinedEntitySubclass2" )
@Table( name = "JoinedEntitySubclass2" )
public static class JoinedEntitySubclass2 extends JoinedEntity {
public JoinedEntitySubclass2() {
}
public JoinedEntitySubclass2(Integer id, String name) {
super( id, name );
}
public JoinedEntitySubclass2(Integer id, String name, JoinedEntity other) {
super( id, name, other );
}
}
@Entity( name = "JoinedEntitySubSubclass2" )
@Table( name = "JoinedEntitySubSubclass2" )
public static class JoinedEntitySubSubclass2 extends JoinedEntitySubclass2 {
public JoinedEntitySubSubclass2() {
}
public JoinedEntitySubSubclass2(Integer id, String name) {
super( id, name );
}
public JoinedEntitySubSubclass2(Integer id, String name, JoinedEntity other) {
super( id, name, other );
}
}
@Entity( name = "DiscriminatorEntity" )
@Table( name = "DiscriminatorEntity" )
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "e_type", discriminatorType = DiscriminatorType.STRING )
@DiscriminatorValue( "B" )
public static class DiscriminatorEntity {
@Id
public Integer id;
public String name;
@ManyToOne( fetch = FetchType.LAZY )
public DiscriminatorEntity other;
public DiscriminatorEntity() {
}
public DiscriminatorEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
public DiscriminatorEntity(
Integer id,
String name,
DiscriminatorEntity other) {
this.id = id;
this.name = name;
this.other = other;
}
}
@Entity( name = "DiscriminatorEntitySubclass" )
@DiscriminatorValue( "S" )
public static class DiscriminatorEntitySubclass extends DiscriminatorEntity {
public DiscriminatorEntitySubclass() {
}
public DiscriminatorEntitySubclass(Integer id, String name) {
super( id, name );
}
public DiscriminatorEntitySubclass(
Integer id,
String name,
DiscriminatorEntity other) {
super( id, name, other );
}
}
@Entity( name = "DiscriminatorEntitySubSubclass" )
@DiscriminatorValue( "SS" )
public static class DiscriminatorEntitySubSubclass extends DiscriminatorEntitySubclass {
public DiscriminatorEntitySubSubclass() {
}
public DiscriminatorEntitySubSubclass(Integer id, String name) {
super( id, name );
}
public DiscriminatorEntitySubSubclass(
Integer id,
String name,
DiscriminatorEntity other) {
super( id, name, other );
}
}
}