HHH-15726 Fix treat disjunction handling and improve pushdown
This commit is contained in:
parent
167a14bcc7
commit
5f99dd3862
|
@ -124,7 +124,7 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
|
|||
appendSql( UNION_ALL );
|
||||
searchIndex = unionIndex + UNION_ALL.length();
|
||||
}
|
||||
append( tableExpression, searchIndex, tableExpression.length() - 2 );
|
||||
append( tableExpression, searchIndex, tableExpression.length() - 1 );
|
||||
renderLockHint( lockMode );
|
||||
appendSql( " )" );
|
||||
|
||||
|
|
|
@ -1093,7 +1093,7 @@ public class BinderHelper {
|
|||
return;
|
||||
}
|
||||
else {
|
||||
ownerClass = getSuperPersistentClass( ownerClass );
|
||||
ownerClass = ownerClass.getSuperPersistentClass();
|
||||
}
|
||||
}
|
||||
throw new AnnotationException(
|
||||
|
@ -1110,24 +1110,9 @@ public class BinderHelper {
|
|||
if ( ownerClass.getTable() == referencedClass.getTable() ) {
|
||||
return true;
|
||||
}
|
||||
referencedClass = getSuperPersistentClass( referencedClass );
|
||||
referencedClass = referencedClass.getSuperPersistentClass();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static PersistentClass getSuperPersistentClass(PersistentClass persistentClass) {
|
||||
return persistentClass.getSuperclass() != null ? persistentClass.getSuperclass()
|
||||
: getSuperPersistentClass( persistentClass.getSuperMappedSuperclass() );
|
||||
}
|
||||
|
||||
private static PersistentClass getSuperPersistentClass(MappedSuperclass mappedSuperclass) {
|
||||
if ( mappedSuperclass != null ) {
|
||||
final PersistentClass superClass = mappedSuperclass.getSuperPersistentClass();
|
||||
if ( superClass != null ) {
|
||||
return superClass;
|
||||
}
|
||||
return getSuperPersistentClass( mappedSuperclass.getSuperMappedSuperclass() );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends SqlAstTr
|
|||
appendSql( UNION_ALL );
|
||||
searchIndex = unionIndex + UNION_ALL.length();
|
||||
}
|
||||
append( tableExpression, searchIndex, tableExpression.length() - 2 );
|
||||
append( tableExpression, searchIndex, tableExpression.length() - 1 );
|
||||
renderLockHint( lockMode );
|
||||
appendSql( " )" );
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.mapping.Restrictable;
|
||||
import org.hibernate.persister.entity.EntityNameUse;
|
||||
import org.hibernate.sql.Template;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
@ -42,6 +43,11 @@ public class FilterHelper {
|
|||
private final boolean[] filterAutoAliasFlags;
|
||||
private final Map<String, String>[] filterAliasTableMaps;
|
||||
private final List<String>[] parameterNames;
|
||||
private final Map<String, String> tableToEntityName;
|
||||
|
||||
public FilterHelper(List<FilterConfiguration> filters, SessionFactoryImplementor factory) {
|
||||
this( filters, null, factory );
|
||||
}
|
||||
|
||||
/**
|
||||
* The map of defined filters. This is expected to be in format
|
||||
|
@ -51,7 +57,7 @@ public class FilterHelper {
|
|||
* @param filters The map of defined filters.
|
||||
* @param factory The session factory
|
||||
*/
|
||||
public FilterHelper(List<FilterConfiguration> filters, SessionFactoryImplementor factory) {
|
||||
public FilterHelper(List<FilterConfiguration> filters, Map<String, String> tableToEntityName, SessionFactoryImplementor factory) {
|
||||
int filterCount = filters.size();
|
||||
|
||||
filterNames = new String[filterCount];
|
||||
|
@ -59,6 +65,7 @@ public class FilterHelper {
|
|||
filterAutoAliasFlags = new boolean[filterCount];
|
||||
filterAliasTableMaps = new Map[filterCount];
|
||||
parameterNames = new List[filterCount];
|
||||
this.tableToEntityName = tableToEntityName;
|
||||
|
||||
filterCount = 0;
|
||||
for ( final FilterConfiguration filter : filters ) {
|
||||
|
@ -145,21 +152,32 @@ public class FilterHelper {
|
|||
public void applyEnabledFilters(
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
FilterAliasGenerator aliasGenerator,
|
||||
Map<String, Filter> enabledFilters) {
|
||||
final FilterPredicate predicate = generateFilterPredicate( aliasGenerator, enabledFilters );
|
||||
Map<String, Filter> enabledFilters,
|
||||
TableGroup tableGroup,
|
||||
SqlAstCreationState creationState) {
|
||||
final FilterPredicate predicate = generateFilterPredicate(
|
||||
aliasGenerator,
|
||||
enabledFilters,
|
||||
tableGroup,
|
||||
creationState
|
||||
);
|
||||
if ( predicate != null ) {
|
||||
predicateConsumer.accept( predicate );
|
||||
}
|
||||
}
|
||||
|
||||
private FilterPredicate generateFilterPredicate(FilterAliasGenerator aliasGenerator, Map<String, Filter> enabledFilters) {
|
||||
private FilterPredicate generateFilterPredicate(
|
||||
FilterAliasGenerator aliasGenerator,
|
||||
Map<String, Filter> enabledFilters,
|
||||
TableGroup tableGroup,
|
||||
SqlAstCreationState creationState) {
|
||||
final FilterPredicate filterPredicate = new FilterPredicate();
|
||||
|
||||
for ( int i = 0, max = filterNames.length; i < max; i++ ) {
|
||||
final String filterName = filterNames[i];
|
||||
final FilterImpl enabledFilter = (FilterImpl) enabledFilters.get( filterName );
|
||||
if ( enabledFilter != null ) {
|
||||
filterPredicate.applyFragment( render( aliasGenerator, i ), enabledFilter, parameterNames[i] );
|
||||
filterPredicate.applyFragment( render( aliasGenerator, i, tableGroup, creationState ), enabledFilter, parameterNames[i] );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,34 +205,78 @@ public class FilterHelper {
|
|||
if ( buffer.length() > 0 ) {
|
||||
buffer.append( " and " );
|
||||
}
|
||||
buffer.append( render( aliasGenerator, i ) );
|
||||
buffer.append( render( aliasGenerator, i, null, null ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String render(FilterAliasGenerator aliasGenerator, int filterIndex) {
|
||||
private String render(
|
||||
FilterAliasGenerator aliasGenerator,
|
||||
int filterIndex,
|
||||
TableGroup tableGroup,
|
||||
SqlAstCreationState creationState) {
|
||||
Map<String, String> aliasTableMap = filterAliasTableMaps[filterIndex];
|
||||
String condition = filterConditions[filterIndex];
|
||||
if ( aliasGenerator == null ) {
|
||||
return StringHelper.replace( condition, FilterImpl.MARKER + ".", "");
|
||||
}
|
||||
if ( filterAutoAliasFlags[filterIndex] ) {
|
||||
return StringHelper.replace(
|
||||
final String tableName = aliasTableMap.get( null );
|
||||
final String newCondition = StringHelper.replace(
|
||||
condition,
|
||||
FilterImpl.MARKER,
|
||||
aliasGenerator.getAlias( aliasTableMap.get( null ) )
|
||||
aliasGenerator.getAlias( tableName )
|
||||
);
|
||||
if ( creationState != null && tableToEntityName != null && !newCondition.equals( condition ) ) {
|
||||
creationState.registerEntityNameUsage(
|
||||
tableGroup,
|
||||
EntityNameUse.EXPRESSION,
|
||||
tableToEntityName.get(
|
||||
tableName == null
|
||||
? tableGroup.getPrimaryTableReference().getTableId()
|
||||
: tableName
|
||||
)
|
||||
);
|
||||
}
|
||||
return newCondition;
|
||||
}
|
||||
else if ( isTableFromPersistentClass( aliasTableMap ) ) {
|
||||
return StringHelper.replace( condition, "{alias}", aliasGenerator.getAlias( aliasTableMap.get( null ) ) );
|
||||
final String tableName = aliasTableMap.get( null );
|
||||
final String newCondition = StringHelper.replace(
|
||||
condition,
|
||||
"{alias}",
|
||||
aliasGenerator.getAlias( tableName )
|
||||
);
|
||||
if ( creationState != null && !newCondition.equals( condition ) ) {
|
||||
creationState.registerEntityNameUsage(
|
||||
tableGroup,
|
||||
EntityNameUse.EXPRESSION,
|
||||
tableToEntityName.get(
|
||||
tableName == null
|
||||
? tableGroup.getPrimaryTableReference().getTableId()
|
||||
: tableName
|
||||
)
|
||||
);
|
||||
}
|
||||
return newCondition;
|
||||
}
|
||||
else {
|
||||
for ( Map.Entry<String, String> entry : aliasTableMap.entrySet() ) {
|
||||
condition = StringHelper.replace( condition,
|
||||
final String tableName = entry.getValue();
|
||||
final String newCondition = StringHelper.replace(
|
||||
condition,
|
||||
"{" + entry.getKey() + "}",
|
||||
aliasGenerator.getAlias( entry.getValue() )
|
||||
aliasGenerator.getAlias( tableName )
|
||||
);
|
||||
if ( creationState != null && !newCondition.equals( condition ) ) {
|
||||
creationState.registerEntityNameUsage(
|
||||
tableGroup,
|
||||
EntityNameUse.EXPRESSION,
|
||||
tableToEntityName.get( tableName )
|
||||
);
|
||||
}
|
||||
condition = newCondition;
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
*/
|
||||
package org.hibernate.loader.ast.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import jakarta.persistence.CacheRetrieveMode;
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
|
@ -20,6 +22,7 @@ import org.hibernate.graph.spi.AppliedGraph;
|
|||
import org.hibernate.metamodel.mapping.AssociationKey;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.query.spi.Limit;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.query.ResultListTransformer;
|
||||
|
@ -35,6 +38,7 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
|||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
|
@ -103,6 +107,21 @@ public class LoaderSqlAstCreationState
|
|||
return processingState.getInflightQueryPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTreat(TableGroup tableGroup, EntityDomainType<?> treatType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTreatUsage(TableGroup tableGroup, EntityDomainType<?> treatType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlExpressionResolver getSqlExpressionResolver() {
|
||||
return processingState;
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.Remove;
|
||||
import org.hibernate.boot.Metadata;
|
||||
|
@ -1331,4 +1332,45 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
|
|||
public List<CheckConstraint> getCheckConstraints() {
|
||||
return checkConstraints;
|
||||
}
|
||||
|
||||
private boolean containsColumn(Column column) {
|
||||
for ( Property declaredProperty : declaredProperties ) {
|
||||
if ( declaredProperty.getSelectables().contains( column ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public boolean isDefinedOnMultipleSubclasses(Column column) {
|
||||
PersistentClass declaringType = null;
|
||||
for ( PersistentClass persistentClass : getSubclassClosure() ) {
|
||||
if ( persistentClass.containsColumn( column ) ) {
|
||||
if ( declaringType != null && declaringType != persistentClass ) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
declaringType = persistentClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public PersistentClass getSuperPersistentClass() {
|
||||
return getSuperclass() != null ? getSuperclass() : getSuperPersistentClass( getSuperMappedSuperclass() );
|
||||
}
|
||||
|
||||
private static PersistentClass getSuperPersistentClass(MappedSuperclass mappedSuperclass) {
|
||||
if ( mappedSuperclass != null ) {
|
||||
final PersistentClass superClass = mappedSuperclass.getSuperPersistentClass();
|
||||
if ( superClass != null ) {
|
||||
return superClass;
|
||||
}
|
||||
return getSuperPersistentClass( mappedSuperclass.getSuperMappedSuperclass() );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.hibernate.mapping.Contributable;
|
|||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.persister.entity.EntityNameUse;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
|
||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||
|
@ -245,6 +246,34 @@ public interface EntityMappingType
|
|||
return superMappingType.getRootEntityDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the table group and its table reference as well as table reference joins
|
||||
* in a way such that unnecessary tables or joins are omitted if possible,
|
||||
* based on the given treated entity names.
|
||||
* <p>
|
||||
* The goal is to e.g. remove join inheritance "branches" or union selects that are impossible.
|
||||
* <p>
|
||||
* Consider the following example:
|
||||
* <code>
|
||||
* class BaseEntity {}
|
||||
* class Sub1 extends BaseEntity {}
|
||||
* class Sub1Sub1 extends Sub1 {}
|
||||
* class Sub1Sub2 extends Sub1 {}
|
||||
* class Sub2 extends BaseEntity {}
|
||||
* class Sub2Sub1 extends Sub2 {}
|
||||
* class Sub2Sub2 extends Sub2 {}
|
||||
* </code>
|
||||
* <p>
|
||||
* If the <code>treatedEntityNames</code> only contains <code>Sub1</code> or any of its subtypes,
|
||||
* this means that <code>Sub2</code> and all subtypes are impossible,
|
||||
* thus the joins/selects for these types shall be omitted in the given table group.
|
||||
*
|
||||
* @param tableGroup The table group to prune subclass tables for
|
||||
* @param entityNameUses The entity names under which a table group was used.
|
||||
*/
|
||||
default void pruneForSubclasses(TableGroup tableGroup, Map<String, EntityNameUse> entityNameUses) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the table group and its table reference as well as table reference joins
|
||||
* in a way such that unnecessary tables or joins are omitted if possible,
|
||||
|
@ -269,7 +298,9 @@ public interface EntityMappingType
|
|||
*
|
||||
* @param tableGroup The table group to prune subclass tables for
|
||||
* @param treatedEntityNames The entity names for which path usages were registered
|
||||
* @deprecated Use {@link #pruneForSubclasses(TableGroup, Map)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
default void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.metamodel.model.domain;
|
|||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -58,7 +59,7 @@ public abstract class AbstractManagedType<J>
|
|||
private volatile Map<String, PluralPersistentAttribute<J, ?, ?>> declaredPluralAttributes ;
|
||||
private volatile Map<String, PersistentAttribute<J, ?>> declaredConcreteGenericAttributes;
|
||||
|
||||
private final List<ManagedDomainType> subTypes = new ArrayList<>();
|
||||
private final List<ManagedDomainType<? extends J>> subTypes = new ArrayList<>();
|
||||
|
||||
protected AbstractManagedType(
|
||||
String hibernateTypeName,
|
||||
|
@ -87,6 +88,11 @@ public abstract class AbstractManagedType<J>
|
|||
return superType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends ManagedDomainType<? extends J>> getSubTypes() {
|
||||
return subTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSubType(ManagedDomainType subType){
|
||||
subTypes.add( subType );
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.model.domain;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.persistence.metamodel.EntityType;
|
||||
|
||||
import org.hibernate.query.sqm.SqmPathSource;
|
||||
|
@ -17,4 +19,7 @@ import org.hibernate.query.sqm.SqmPathSource;
|
|||
*/
|
||||
public interface EntityDomainType<J> extends IdentifiableDomainType<J>, EntityType<J>, SqmPathSource<J> {
|
||||
String getHibernateEntityName();
|
||||
|
||||
@Override
|
||||
Collection<? extends EntityDomainType<? extends J>> getSubTypes();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.model.domain;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.graph.spi.SubGraphImplementor;
|
||||
|
@ -41,6 +42,8 @@ public interface ManagedDomainType<J> extends SqmExpressible<J>, DomainType<J>,
|
|||
*/
|
||||
ManagedDomainType<? super J> getSuperType();
|
||||
|
||||
Collection<? extends ManagedDomainType<? extends J>> getSubTypes();
|
||||
|
||||
void addSubType(ManagedDomainType subType);
|
||||
|
||||
void visitAttributes(Consumer<? super PersistentAttribute<J, ?>> action);
|
||||
|
|
|
@ -8,6 +8,8 @@ package org.hibernate.metamodel.model.domain.internal;
|
|||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.persistence.metamodel.EntityType;
|
||||
|
||||
import org.hibernate.graph.internal.SubGraphImpl;
|
||||
|
@ -21,6 +23,7 @@ import org.hibernate.metamodel.model.domain.DomainType;
|
|||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
|
||||
import org.hibernate.metamodel.model.domain.JpaMetamodel;
|
||||
import org.hibernate.metamodel.model.domain.ManagedDomainType;
|
||||
import org.hibernate.metamodel.model.domain.PersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
|
||||
|
@ -169,6 +172,12 @@ public class EntityTypeImpl<J>
|
|||
return super.getSuperType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends EntityDomainType<? extends J>> getSubTypes() {
|
||||
//noinspection unchecked
|
||||
return (Collection<? extends EntityDomainType<? extends J>>) super.getSubTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends J> SubGraphImplementor<S> makeSubGraph(Class<S> subType) {
|
||||
|
|
|
@ -85,6 +85,7 @@ import org.hibernate.persister.collection.mutation.CollectionMutationTarget;
|
|||
import org.hibernate.persister.collection.mutation.CollectionTableMapping;
|
||||
import org.hibernate.persister.collection.mutation.RemoveCoordinator;
|
||||
import org.hibernate.persister.collection.mutation.RowMutationOperations;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
import org.hibernate.persister.entity.PropertyMapping;
|
||||
|
@ -533,7 +534,16 @@ public abstract class AbstractCollectionPersister
|
|||
filterHelper = null;
|
||||
}
|
||||
else {
|
||||
filterHelper = new FilterHelper( collectionBootDescriptor.getFilters(), factory);
|
||||
final Map<String, String> entityNameByTableNameMap;
|
||||
if ( elementPersister == null ) {
|
||||
entityNameByTableNameMap = null;
|
||||
}
|
||||
else {
|
||||
entityNameByTableNameMap = AbstractEntityPersister.getEntityNameByTableNameMap(
|
||||
creationContext.getBootModel().getEntityBinding( elementPersister.getEntityName() )
|
||||
);
|
||||
}
|
||||
filterHelper = new FilterHelper( collectionBootDescriptor.getFilters(), entityNameByTableNameMap, factory );
|
||||
}
|
||||
|
||||
// Handle any filters applied to this collectionBinding for many-to-many
|
||||
|
@ -1186,12 +1196,19 @@ public abstract class AbstractCollectionPersister
|
|||
}
|
||||
|
||||
@Override
|
||||
public void applyFilterRestrictions(Consumer<Predicate> predicateConsumer, TableGroup tableGroup, boolean useQualifier, Map<String, Filter> enabledFilters, SqlAstCreationState creationState) {
|
||||
public void applyFilterRestrictions(
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
TableGroup tableGroup,
|
||||
boolean useQualifier,
|
||||
Map<String, Filter> enabledFilters,
|
||||
SqlAstCreationState creationState) {
|
||||
if ( filterHelper != null ) {
|
||||
filterHelper.applyEnabledFilters(
|
||||
predicateConsumer,
|
||||
getFilterAliasGenerator( tableGroup ),
|
||||
enabledFilters
|
||||
enabledFilters,
|
||||
tableGroup,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1200,7 +1217,13 @@ public abstract class AbstractCollectionPersister
|
|||
public abstract boolean isManyToMany();
|
||||
|
||||
@Override
|
||||
public void applyBaseManyToManyRestrictions(Consumer<Predicate> predicateConsumer, TableGroup tableGroup, boolean useQualifier, Map<String, Filter> enabledFilters, Set<String> treatAsDeclarations, SqlAstCreationState creationState) {
|
||||
public void applyBaseManyToManyRestrictions(
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
TableGroup tableGroup,
|
||||
boolean useQualifier,
|
||||
Map<String, Filter> enabledFilters,
|
||||
Set<String> treatAsDeclarations,
|
||||
SqlAstCreationState creationState) {
|
||||
if ( manyToManyFilterHelper == null && manyToManyWhereTemplate == null ) {
|
||||
return;
|
||||
}
|
||||
|
@ -1208,7 +1231,13 @@ public abstract class AbstractCollectionPersister
|
|||
|
||||
if ( manyToManyFilterHelper != null ) {
|
||||
final FilterAliasGenerator aliasGenerator = elementPersister.getFilterAliasGenerator( tableGroup );
|
||||
manyToManyFilterHelper.applyEnabledFilters( predicateConsumer, aliasGenerator, enabledFilters );
|
||||
manyToManyFilterHelper.applyEnabledFilters(
|
||||
predicateConsumer,
|
||||
aliasGenerator,
|
||||
enabledFilters,
|
||||
tableGroup,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
if ( manyToManyWhereString != null ) {
|
||||
|
|
|
@ -110,6 +110,7 @@ import org.hibernate.internal.CoreLogging;
|
|||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.FilterAliasGenerator;
|
||||
import org.hibernate.internal.FilterHelper;
|
||||
import org.hibernate.internal.FilterImpl;
|
||||
import org.hibernate.internal.util.IndexedConsumer;
|
||||
import org.hibernate.internal.util.LazyValue;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
|
@ -143,6 +144,7 @@ import org.hibernate.mapping.Column;
|
|||
import org.hibernate.mapping.Component;
|
||||
import org.hibernate.mapping.DependantValue;
|
||||
import org.hibernate.mapping.Formula;
|
||||
import org.hibernate.mapping.Join;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.Selectable;
|
||||
|
@ -358,6 +360,7 @@ public abstract class AbstractEntityPersister
|
|||
private final String[][] propertyColumnWriters;
|
||||
private final boolean[][] propertyColumnUpdateable;
|
||||
private final boolean[][] propertyColumnInsertable;
|
||||
private final Set<String> sharedColumnNames;
|
||||
|
||||
private final List<Integer> lobProperties;
|
||||
|
||||
|
@ -434,6 +437,7 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
protected ReflectionOptimizer.AccessOptimizer accessOptimizer;
|
||||
|
||||
protected final String[] fullDiscriminatorSQLValues;
|
||||
private final Object[] fullDiscriminatorValues;
|
||||
|
||||
/**
|
||||
|
@ -599,6 +603,7 @@ public abstract class AbstractEntityPersister
|
|||
propertyColumnWriters = new String[hydrateSpan][];
|
||||
propertyColumnUpdateable = new boolean[hydrateSpan][];
|
||||
propertyColumnInsertable = new boolean[hydrateSpan][];
|
||||
sharedColumnNames = new HashSet<>();
|
||||
|
||||
final HashSet<Property> thisClassProperties = new HashSet<>();
|
||||
final ArrayList<String> lazyNames = new ArrayList<>();
|
||||
|
@ -736,6 +741,10 @@ public abstract class AbstractEntityPersister
|
|||
typeConfiguration,
|
||||
functionRegistry
|
||||
);
|
||||
if ( isDefinedBySubclass && persistentClass.isDefinedOnMultipleSubclasses( column )
|
||||
|| !isDefinedBySubclass && persistentClass.hasSubclasses() ) {
|
||||
sharedColumnNames.add( colName );
|
||||
}
|
||||
}
|
||||
}
|
||||
propColumns.add( cols );
|
||||
|
@ -772,7 +781,7 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
// Handle any filters applied to the class level
|
||||
filterHelper = isNotEmpty( persistentClass.getFilters() )
|
||||
? new FilterHelper( persistentClass.getFilters(), factory )
|
||||
? new FilterHelper( persistentClass.getFilters(), getEntityNameByTableNameMap( persistentClass ), factory )
|
||||
: null;
|
||||
|
||||
useReferenceCacheEntries = shouldUseReferenceCacheEntries( creationContext.getSessionFactoryOptions() );
|
||||
|
@ -782,12 +791,12 @@ public abstract class AbstractEntityPersister
|
|||
&& shouldInvalidateCache( persistentClass, creationContext );
|
||||
|
||||
final List<Object> values = new ArrayList<>();
|
||||
// final List<String> sqlValues = new ArrayList<>();
|
||||
final List<String> sqlValues = new ArrayList<>();
|
||||
|
||||
if ( persistentClass.isPolymorphic() && persistentClass.getDiscriminator() != null ) {
|
||||
if ( !getEntityMetamodel().isAbstract() ) {
|
||||
values.add( DiscriminatorHelper.getDiscriminatorValue( persistentClass ) );
|
||||
// sqlValues.add( DiscriminatorHelper.getDiscriminatorSQLValue( persistentClass, dialect, factory ) );
|
||||
sqlValues.add( DiscriminatorHelper.getDiscriminatorSQLValue( persistentClass, dialect ) );
|
||||
}
|
||||
|
||||
final List<Subclass> subclasses = persistentClass.getSubclasses();
|
||||
|
@ -796,15 +805,34 @@ public abstract class AbstractEntityPersister
|
|||
//copy/paste from EntityMetamodel:
|
||||
if ( !isAbstract( subclass ) ) {
|
||||
values.add( DiscriminatorHelper.getDiscriminatorValue( subclass ) );
|
||||
// sqlValues.add( DiscriminatorHelper.getDiscriminatorSQLValue( subclass, dialect, factory ) );
|
||||
sqlValues.add( DiscriminatorHelper.getDiscriminatorSQLValue( subclass, dialect ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fullDiscriminatorSQLValues = toStringArray( sqlValues );
|
||||
fullDiscriminatorSQLValues = toStringArray( sqlValues );
|
||||
fullDiscriminatorValues = toObjectArray( values );
|
||||
}
|
||||
|
||||
public static Map<String, String> getEntityNameByTableNameMap(PersistentClass persistentClass) {
|
||||
final Map<String, String> entityNameByTableNameMap = new HashMap<>();
|
||||
PersistentClass superType = persistentClass.getSuperPersistentClass();
|
||||
while ( superType != null ) {
|
||||
entityNameByTableNameMap.put( superType.getTable().getName(), superType.getEntityName() );
|
||||
for ( Join join : superType.getJoins() ) {
|
||||
entityNameByTableNameMap.put( join.getTable().getName(), superType.getEntityName() );
|
||||
}
|
||||
superType = superType.getSuperPersistentClass();
|
||||
}
|
||||
for ( PersistentClass subclass : persistentClass.getSubclassClosure() ) {
|
||||
entityNameByTableNameMap.put( subclass.getTable().getName(), subclass.getEntityName() );
|
||||
for ( Join join : subclass.getJoins() ) {
|
||||
entityNameByTableNameMap.put( join.getTable().getName(), subclass.getEntityName() );
|
||||
}
|
||||
}
|
||||
return entityNameByTableNameMap;
|
||||
}
|
||||
|
||||
private MultiIdEntityLoader<Object> buildMultiIdLoader(PersistentClass persistentClass) {
|
||||
if ( persistentClass.getIdentifier() instanceof BasicValue
|
||||
&& MultiKeyLoadHelper.supportsSqlArrayType( factory.getServiceRegistry().getService( JdbcServices.class ).getDialect() ) ) {
|
||||
|
@ -945,6 +973,10 @@ public abstract class AbstractEntityPersister
|
|||
return entityMetamodel.getSubclassEntityNames().contains( entityName );
|
||||
}
|
||||
|
||||
public boolean isSharedColumn(String columnExpression) {
|
||||
return sharedColumnNames.contains( columnExpression );
|
||||
}
|
||||
|
||||
protected boolean[] getTableHasColumns() {
|
||||
return tableHasColumns;
|
||||
}
|
||||
|
@ -3007,7 +3039,13 @@ public abstract class AbstractEntityPersister
|
|||
final FilterAliasGenerator filterAliasGenerator = useQualifier && tableGroup != null
|
||||
? getFilterAliasGenerator( tableGroup )
|
||||
: null;
|
||||
filterHelper.applyEnabledFilters( predicateConsumer, filterAliasGenerator, enabledFilters );
|
||||
filterHelper.applyEnabledFilters(
|
||||
predicateConsumer,
|
||||
filterAliasGenerator,
|
||||
enabledFilters,
|
||||
tableGroup,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.persister.entity;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
/**
|
||||
* Describes the kind of entity name use.
|
||||
*/
|
||||
@Incubating
|
||||
public final class EntityNameUse {
|
||||
|
||||
public static final EntityNameUse PROJECTION = new EntityNameUse( UseKind.PROJECTION, true );
|
||||
public static final EntityNameUse EXPRESSION = new EntityNameUse( UseKind.EXPRESSION, true );
|
||||
public static final EntityNameUse TREAT = new EntityNameUse( UseKind.TREAT, true );
|
||||
public static final EntityNameUse OPTIONAL_TREAT = new EntityNameUse( UseKind.TREAT, false );
|
||||
public static final EntityNameUse FILTER = new EntityNameUse( UseKind.FILTER, true );
|
||||
|
||||
private final UseKind kind;
|
||||
private final boolean requiresRestriction;
|
||||
|
||||
private EntityNameUse(UseKind kind, boolean requiresRestriction) {
|
||||
this.kind = kind;
|
||||
this.requiresRestriction = requiresRestriction;
|
||||
}
|
||||
|
||||
private static EntityNameUse get(UseKind kind) {
|
||||
switch ( kind ) {
|
||||
case PROJECTION:
|
||||
return PROJECTION;
|
||||
case EXPRESSION:
|
||||
return EXPRESSION;
|
||||
case TREAT:
|
||||
return TREAT;
|
||||
case FILTER:
|
||||
return FILTER;
|
||||
}
|
||||
throw new IllegalArgumentException( "Unknown kind: " + kind );
|
||||
}
|
||||
|
||||
public UseKind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public boolean requiresRestriction() {
|
||||
return requiresRestriction;
|
||||
}
|
||||
|
||||
public EntityNameUse stronger(EntityNameUse other) {
|
||||
return other == null || kind.isStrongerThan( other.kind ) ? this : get( other.kind );
|
||||
}
|
||||
|
||||
public EntityNameUse weaker(EntityNameUse other) {
|
||||
return other == null || kind.isWeakerThan( other.kind ) ? this : get( other.kind );
|
||||
}
|
||||
|
||||
public enum UseKind {
|
||||
/**
|
||||
* An entity type is used through a path that appears in the {@code SELECT} clause somehow.
|
||||
* This use kind is registered for top level select items or join fetches.
|
||||
*/
|
||||
PROJECTION,
|
||||
/**
|
||||
* An entity type is used through a path expression, but doesn't match the criteria for {@link #PROJECTION}.
|
||||
*/
|
||||
EXPRESSION,
|
||||
/**
|
||||
* An entity type is used through a treat expression.
|
||||
*/
|
||||
TREAT,
|
||||
/**
|
||||
* An entity type is filtered for through a type restriction predicate i.e. {@code type(alias) = Subtype}.
|
||||
*/
|
||||
FILTER;
|
||||
|
||||
public boolean isStrongerThan(UseKind other) {
|
||||
return ordinal() > other.ordinal();
|
||||
}
|
||||
|
||||
public UseKind stronger(UseKind other) {
|
||||
return other == null || isStrongerThan( other ) ? this : other;
|
||||
}
|
||||
|
||||
public boolean isWeakerThan(UseKind other) {
|
||||
return ordinal() < other.ordinal();
|
||||
}
|
||||
|
||||
public UseKind weaker(UseKind other) {
|
||||
return other == null || isWeakerThan( other ) ? this : other;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -162,6 +162,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
|
||||
// Span of the tables directly mapped by this entity and super-classes, if any
|
||||
private final int coreTableSpan;
|
||||
private final int subclassCoreTableSpan;
|
||||
// only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
|
||||
private final boolean[] isNullableTable;
|
||||
private final boolean[] isInverseTable;
|
||||
|
@ -274,6 +275,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
|
||||
//Span of the tableNames directly mapped by this entity and super-classes, if any
|
||||
coreTableSpan = tableNames.size();
|
||||
subclassCoreTableSpan = persistentClass.getSubclassTableClosure().size();
|
||||
tableSpan = persistentClass.getJoinClosureSpan() + coreTableSpan;
|
||||
|
||||
isNullableTable = new boolean[tableSpan];
|
||||
|
@ -1302,57 +1304,104 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
|
||||
final Set<TableReference> retainedTableReferences = new HashSet<>( treatedEntityNames.size() );
|
||||
final Set<String> sharedSuperclassTables = new HashSet<>();
|
||||
public void pruneForSubclasses(TableGroup tableGroup, Map<String, EntityNameUse> entityNameUses) {
|
||||
final Set<TableReference> retainedTableReferences = new HashSet<>( entityNameUses.size() );
|
||||
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
|
||||
|
||||
for ( String treatedEntityName : treatedEntityNames ) {
|
||||
final JoinedSubclassEntityPersister persister =
|
||||
(JoinedSubclassEntityPersister) metamodel.findEntityDescriptor( treatedEntityName );
|
||||
final String[] subclassTableNames = persister.getSubclassTableNames();
|
||||
// For every treated entity name, we collect table names that are needed by all treated entity names
|
||||
// In mathematical terms, sharedSuperclassTables will be the "intersection" of the table names of all treated entities
|
||||
if ( sharedSuperclassTables.isEmpty() ) {
|
||||
for ( int i = 0; i < subclassTableNames.length; i++ ) {
|
||||
if ( persister.isClassOrSuperclassTable[i] ) {
|
||||
sharedSuperclassTables.add( subclassTableNames[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sharedSuperclassTables.retainAll( Arrays.asList( subclassTableNames ) );
|
||||
}
|
||||
// Add the table references for all table names of the treated entities as we have to retain these table references.
|
||||
// Table references not appearing in this set can later be pruned away
|
||||
for ( String subclassTableName : subclassTableNames ) {
|
||||
final TableReference tableReference =
|
||||
tableGroup.getTableReference( null, subclassTableName, false );
|
||||
if ( tableReference == null ) {
|
||||
throw new UnknownTableReferenceException( getRootTableName(), "Couldn't find table reference" );
|
||||
}
|
||||
retainedTableReferences.add( tableReference );
|
||||
}
|
||||
}
|
||||
final List<TableReferenceJoin> tableReferenceJoins = tableGroup.getTableReferenceJoins();
|
||||
// The optimization is to remove all table reference joins that are not contained in the retainedTableReferences
|
||||
// In addition, we switch from a possible LEFT join, to an inner join for all sharedSuperclassTables
|
||||
// For now, we can only do this if the table group reports canUseInnerJoins or isRealTableGroup,
|
||||
// We can only do this optimization if the table group reports canUseInnerJoins or isRealTableGroup,
|
||||
// because the switch for table reference joins to INNER must be cardinality preserving.
|
||||
// If canUseInnerJoins is true, this is trivially given, but also if the table group is real
|
||||
// i.e. with parenthesis around, as that means the table reference joins will be isolated
|
||||
if ( tableGroup.canUseInnerJoins() || tableGroup.isRealTableGroup() ) {
|
||||
final boolean innerJoinOptimization = tableGroup.canUseInnerJoins() || tableGroup.isRealTableGroup();
|
||||
final Set<String> tablesToInnerJoin = innerJoinOptimization ? new HashSet<>() : null;
|
||||
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
|
||||
final JoinedSubclassEntityPersister persister =
|
||||
(JoinedSubclassEntityPersister) metamodel.findEntityDescriptor( entry.getKey() );
|
||||
// The following block tries to figure out what can be inner joined and which super class table joins can be omitted
|
||||
if ( innerJoinOptimization && ( useKind == EntityNameUse.UseKind.TREAT || useKind == EntityNameUse.UseKind.FILTER ) ) {
|
||||
final String[] subclassTableNames = persister.getSubclassTableNames();
|
||||
// Build the intersection of all tables names that are of the class or super class
|
||||
// These are the tables that can be safely inner joined
|
||||
if ( tablesToInnerJoin.isEmpty() ) {
|
||||
for ( int i = 0; i < subclassTableNames.length; i++ ) {
|
||||
if ( persister.isClassOrSuperclassTable[i] ) {
|
||||
tablesToInnerJoin.add( subclassTableNames[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tablesToInnerJoin.retainAll( Arrays.asList( subclassTableNames ) );
|
||||
}
|
||||
if ( useKind == EntityNameUse.UseKind.FILTER && explicitDiscriminatorColumnName == null ) {
|
||||
// If there is no discriminator column,
|
||||
// we must retain all joins to subclass tables to be able to discriminate the rows
|
||||
for ( int i = 0; i < subclassTableNames.length; i++ ) {
|
||||
if ( !persister.isClassOrSuperclassTable[i] ) {
|
||||
final String subclassTableName = subclassTableNames[i];
|
||||
final TableReference mainTableReference = tableGroup.getTableReference(
|
||||
null,
|
||||
subclassTableName,
|
||||
false
|
||||
);
|
||||
if ( mainTableReference == null ) {
|
||||
throw new UnknownTableReferenceException(
|
||||
subclassTableName,
|
||||
"Couldn't find table reference"
|
||||
);
|
||||
}
|
||||
retainedTableReferences.add( mainTableReference );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final TableReference mainTableReference = tableGroup.getTableReference(
|
||||
null,
|
||||
persister.getTableName(),
|
||||
false
|
||||
);
|
||||
if ( mainTableReference == null ) {
|
||||
throw new UnknownTableReferenceException( persister.getTableName(), "Couldn't find table reference" );
|
||||
}
|
||||
retainedTableReferences.add( mainTableReference );
|
||||
}
|
||||
// If no tables to inner join have been found, we add at least the super class tables of this persister
|
||||
if ( innerJoinOptimization && tablesToInnerJoin.isEmpty() ) {
|
||||
final String[] subclassTableNames = getSubclassTableNames();
|
||||
for ( int i = 0; i < subclassTableNames.length; i++ ) {
|
||||
if ( isClassOrSuperclassTable[i] ) {
|
||||
tablesToInnerJoin.add( subclassTableNames[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<TableReferenceJoin> tableReferenceJoins = tableGroup.getTableReferenceJoins();
|
||||
if ( tableReferenceJoins.isEmpty() ) {
|
||||
return;
|
||||
}
|
||||
// The optimization is to remove all table reference joins that are not contained in the retainedTableReferences
|
||||
// In addition, we switch from a possible LEFT join, to an INNER join for all tablesToInnerJoin
|
||||
if ( innerJoinOptimization ) {
|
||||
final TableReferenceJoin[] oldJoins = tableReferenceJoins.toArray( new TableReferenceJoin[0] );
|
||||
tableReferenceJoins.clear();
|
||||
for ( TableReferenceJoin oldJoin : oldJoins ) {
|
||||
final NamedTableReference joinedTableReference = oldJoin.getJoinedTableReference();
|
||||
if ( retainedTableReferences.contains( joinedTableReference ) ) {
|
||||
final TableReferenceJoin join = oldJoin.getJoinType() != SqlAstJoinType.INNER
|
||||
&& sharedSuperclassTables.contains( joinedTableReference.getTableExpression() )
|
||||
? new TableReferenceJoin(true, joinedTableReference, oldJoin.getPredicate())
|
||||
&& tablesToInnerJoin.contains( joinedTableReference.getTableExpression() )
|
||||
? new TableReferenceJoin( true, joinedTableReference, oldJoin.getPredicate() )
|
||||
: oldJoin;
|
||||
tableReferenceJoins.add( join );
|
||||
}
|
||||
else {
|
||||
final String tableExpression = oldJoin.getJoinedTableReference().getTableExpression();
|
||||
for ( int i = subclassCoreTableSpan; i < subclassTableNameClosure.length; i++ ) {
|
||||
if ( tableExpression.equals( subclassTableNameClosure[i] ) ) {
|
||||
// Retain joins to secondary tables
|
||||
tableReferenceJoins.add( oldJoin );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -8,10 +8,10 @@ package org.hibernate.persister.entity;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Internal;
|
||||
|
@ -23,6 +23,7 @@ import org.hibernate.dialect.Dialect;
|
|||
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
||||
import org.hibernate.internal.DynamicFilterAliasGenerator;
|
||||
import org.hibernate.internal.FilterAliasGenerator;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.jdbc.Expectation;
|
||||
import org.hibernate.mapping.Column;
|
||||
|
@ -34,13 +35,13 @@ import org.hibernate.mapping.Selectable;
|
|||
import org.hibernate.mapping.Subclass;
|
||||
import org.hibernate.mapping.Table;
|
||||
import org.hibernate.mapping.Value;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.TableDetails;
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||
import org.hibernate.persister.spi.PersisterCreationContext;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||
import org.hibernate.sql.InFragment;
|
||||
import org.hibernate.sql.Template;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
||||
|
@ -660,12 +661,36 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
|
||||
if ( !needsDiscriminator() && treatedEntityNames.isEmpty() ) {
|
||||
public void pruneForSubclasses(TableGroup tableGroup, Map<String, EntityNameUse> entityNameUses) {
|
||||
if ( !needsDiscriminator() && entityNameUses.isEmpty() ) {
|
||||
return;
|
||||
}
|
||||
// The following optimization is to add the discriminator filter fragment for all treated entity names
|
||||
final MappingMetamodelImplementor mappingMetamodel = getFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
|
||||
boolean containsTreatUse = false;
|
||||
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
|
||||
if ( useKind == EntityNameUse.UseKind.PROJECTION || useKind == EntityNameUse.UseKind.EXPRESSION ) {
|
||||
// We only care about treat and filter uses which allow to reduce the amount of rows to select
|
||||
continue;
|
||||
}
|
||||
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entry.getKey() );
|
||||
// Filtering for abstract entities makes no sense, so ignore that
|
||||
// Also, it makes no sense to filter for any of the super types,
|
||||
// as the query will contain a filter for that already anyway
|
||||
if ( !persister.isAbstract() && !isTypeOrSuperType( persister ) && useKind == EntityNameUse.UseKind.TREAT ) {
|
||||
containsTreatUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !containsTreatUse ) {
|
||||
// If we only have FILTER uses, we don't have to do anything here,
|
||||
// because the BaseSqmToSqlAstConverter will already apply the type filter in the WHERE clause
|
||||
return;
|
||||
}
|
||||
// The optimization is to simply add the discriminator filter fragment for all treated entity names
|
||||
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
|
||||
|
||||
final InFragment frag = new InFragment();
|
||||
if ( isDiscriminatorFormula() ) {
|
||||
|
@ -674,34 +699,55 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
|||
else {
|
||||
frag.setColumn( "t", getDiscriminatorColumnName() );
|
||||
}
|
||||
|
||||
final MappingMetamodelImplementor mappingMetamodel = getFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
for ( String subclass : treatedEntityNames ) {
|
||||
final EntityMappingType treatTargetType = mappingMetamodel.getEntityDescriptor( subclass );
|
||||
if ( !treatTargetType.isAbstract() ) {
|
||||
frag.addValue( treatTargetType.getDiscriminatorSQLValue() );
|
||||
boolean containsNotNull = false;
|
||||
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
|
||||
if ( useKind == EntityNameUse.UseKind.PROJECTION || useKind == EntityNameUse.UseKind.EXPRESSION ) {
|
||||
// We only care about treat and filter uses which allow to reduce the amount of rows to select
|
||||
continue;
|
||||
}
|
||||
if ( treatTargetType.hasSubclasses() ) {
|
||||
// if the treat is an abstract class, add the concrete implementations to values if any
|
||||
final Set<String> actualSubClasses = treatTargetType.getSubclassEntityNames();
|
||||
for ( String actualSubClass : actualSubClasses ) {
|
||||
if ( actualSubClass.equals( subclass ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final EntityMappingType actualEntityDescriptor = mappingMetamodel.getEntityDescriptor( actualSubClass );
|
||||
if ( !actualEntityDescriptor.hasSubclasses() ) {
|
||||
frag.addValue( actualEntityDescriptor.getDiscriminatorSQLValue() );
|
||||
}
|
||||
}
|
||||
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entry.getKey() );
|
||||
// Filtering for abstract entities makes no sense, so ignore that
|
||||
// Also, it makes no sense to filter for any of the super types,
|
||||
// as the query will contain a filter for that already anyway
|
||||
if ( !persister.isAbstract() && ( this == persister || !isTypeOrSuperType( persister ) ) ) {
|
||||
containsNotNull = containsNotNull || InFragment.NOT_NULL.equals( persister.getDiscriminatorSQLValue() );
|
||||
frag.addValue( persister.getDiscriminatorSQLValue() );
|
||||
}
|
||||
}
|
||||
final List<String> discriminatorSQLValues = Arrays.asList( ( (SingleTableEntityPersister) getRootEntityDescriptor() ).fullDiscriminatorSQLValues );
|
||||
if ( frag.getValues().size() == discriminatorSQLValues.size() ) {
|
||||
// Nothing to prune if we filter for all subtypes
|
||||
return;
|
||||
}
|
||||
|
||||
tableReference.setPrunedTableExpression(
|
||||
"(select * from " + getTableName() + " t where " + frag.toFragmentString() + ")"
|
||||
);
|
||||
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
|
||||
if ( containsNotNull ) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String lhs;
|
||||
if ( isDiscriminatorFormula() ) {
|
||||
lhs = StringHelper.replace( getDiscriminatorFormulaTemplate(), Template.TEMPLATE, "t" );
|
||||
}
|
||||
else {
|
||||
lhs = "t." + getDiscriminatorColumnName();
|
||||
}
|
||||
sb.append( " or " ).append( lhs ).append( " is not in (" );
|
||||
for ( Object discriminatorSQLValue : discriminatorSQLValues ) {
|
||||
if ( !frag.getValues().contains( discriminatorSQLValue ) ) {
|
||||
sb.append( lhs ).append( discriminatorSQLValue );
|
||||
}
|
||||
}
|
||||
sb.append( ") and " ).append( lhs ).append( " is not null" );
|
||||
frag.getValues().remove( InFragment.NOT_NULL );
|
||||
tableReference.setPrunedTableExpression(
|
||||
"(select * from " + getTableName() + " t where " + frag.toFragmentString() + sb + ")"
|
||||
);
|
||||
}
|
||||
else {
|
||||
tableReference.setPrunedTableExpression(
|
||||
"(select * from " + getTableName() + " t where " + frag.toFragmentString() + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.persister.entity;
|
|||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -53,8 +54,6 @@ import org.hibernate.persister.spi.PersisterCreationContext;
|
|||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
|
||||
|
@ -421,13 +420,13 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
|
||||
public void pruneForSubclasses(TableGroup tableGroup, Map<String, EntityNameUse> entityNameUses) {
|
||||
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference( getRootTableName() );
|
||||
if ( tableReference == null ) {
|
||||
throw new UnknownTableReferenceException( getRootTableName(), "Couldn't find table reference" );
|
||||
}
|
||||
// Replace the default union sub-query with a specially created one that only selects the tables for the treated entity names
|
||||
tableReference.setPrunedTableExpression( generateSubquery( treatedEntityNames ) );
|
||||
tableReference.setPrunedTableExpression( generateSubquery( entityNameUses ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -493,9 +492,9 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
|
||||
final StringBuilder subquery = new StringBuilder()
|
||||
.append( "( " );
|
||||
.append( "(" );
|
||||
|
||||
List<PersistentClass> classes = new JoinedList<>(
|
||||
final List<PersistentClass> classes = new JoinedList<>(
|
||||
List.of( model ),
|
||||
Collections.unmodifiableList( model.getSubclasses() )
|
||||
);
|
||||
|
@ -504,7 +503,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
Table table = clazz.getTable();
|
||||
if ( !table.isAbstractUnionTable() ) {
|
||||
//TODO: move to .sql package!!
|
||||
if ( subquery.length() > 2 ) {
|
||||
if ( subquery.length() > 1 ) {
|
||||
subquery.append( " union " );
|
||||
if ( dialect.supportsUnionAll() ) {
|
||||
subquery.append( "all " );
|
||||
|
@ -526,41 +525,64 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
}
|
||||
|
||||
return subquery.append( " )" ).toString();
|
||||
return subquery.append( ")" ).toString();
|
||||
}
|
||||
|
||||
protected String generateSubquery(Set<String> treated) {
|
||||
protected String generateSubquery(Map<String, EntityNameUse> entityNameUses) {
|
||||
if ( !hasSubclasses() ) {
|
||||
return getTableName();
|
||||
}
|
||||
|
||||
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
||||
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
|
||||
|
||||
// Collect all selectables of every entity subtype and group by selection expression as well as table name
|
||||
final LinkedHashMap<String, Map<String, SelectableMapping>> selectables = new LinkedHashMap<>();
|
||||
// Collect the concrete subclass table names for the treated entity names
|
||||
final Set<String> treatedTableNames = new HashSet<>( treated.size() );
|
||||
for ( String subclassName : treated ) {
|
||||
final UnionSubclassEntityPersister subPersister =
|
||||
(UnionSubclassEntityPersister) metamodel.getEntityDescriptor( subclassName );
|
||||
// Collect all the real (non-abstract) table names
|
||||
treatedTableNames.addAll( Arrays.asList( subPersister.getConstraintOrderedTableNameClosure() ) );
|
||||
final Set<String> tablesToUnion = new HashSet<>( entityNameUses.size() );
|
||||
// Check if there are filter uses and if so, we know the set of tables to union already
|
||||
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||
final UnionSubclassEntityPersister persister =
|
||||
(UnionSubclassEntityPersister) metamodel.getEntityDescriptor( entry.getKey() );
|
||||
if ( entry.getValue().getKind() == EntityNameUse.UseKind.FILTER && !persister.isAbstract() ) {
|
||||
tablesToUnion.add( persister.getRootTableName() );
|
||||
}
|
||||
// Collect selectables grouped by the table names in which they appear
|
||||
// TODO: we could cache this
|
||||
subPersister.collectSelectableOwners( selectables );
|
||||
persister.collectSelectableOwners( selectables );
|
||||
}
|
||||
|
||||
if ( tablesToUnion.isEmpty() ) {
|
||||
// If there are no filter uses, we try to find the most specific treat uses and union all their subclass tables
|
||||
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||
if ( entry.getValue().getKind() == EntityNameUse.UseKind.TREAT ) {
|
||||
// Collect all the real (non-abstract) table names
|
||||
final UnionSubclassEntityPersister persister =
|
||||
(UnionSubclassEntityPersister) metamodel.getEntityDescriptor( entry.getKey() );
|
||||
tablesToUnion.addAll( Arrays.asList( persister.getConstraintOrderedTableNameClosure() ) );
|
||||
}
|
||||
}
|
||||
if ( tablesToUnion.isEmpty() ) {
|
||||
// If there are only projection or expression uses, we can't optimize anything
|
||||
return getTableName();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a union sub-query for the table names, like generateSubquery(PersistentClass model, Mapping mapping)
|
||||
final StringBuilder buf = new StringBuilder( subquery.length() )
|
||||
.append( "( " );
|
||||
final StringBuilderSqlAppender sqlAppender = new StringBuilderSqlAppender( buf );
|
||||
final StringBuilder buf = new StringBuilder( subquery.length() ).append( "(" );
|
||||
|
||||
for ( EntityMappingType mappingType : getSubMappingTypes() ) {
|
||||
final Collection<EntityMappingType> subMappingTypes = getSubMappingTypes();
|
||||
final ArrayList<EntityMappingType> subMappingTypesAndThis = new ArrayList<>( subMappingTypes.size() + 1 );
|
||||
subMappingTypesAndThis.add( this );
|
||||
subMappingTypesAndThis.addAll( subMappingTypes );
|
||||
for ( EntityMappingType mappingType : subMappingTypesAndThis ) {
|
||||
final AbstractEntityPersister persister = (AbstractEntityPersister) mappingType;
|
||||
final String subclassTableName = persister.getTableName();
|
||||
if ( treatedTableNames.contains( subclassTableName ) ) {
|
||||
if ( buf.length() > 2 ) {
|
||||
final String subclassTableName;
|
||||
if ( persister.hasSubclasses() ) {
|
||||
subclassTableName = persister.getRootTableName();
|
||||
}
|
||||
else {
|
||||
subclassTableName = persister.getTableName();
|
||||
}
|
||||
if ( tablesToUnion.contains( subclassTableName ) ) {
|
||||
if ( buf.length() > 1 ) {
|
||||
buf.append(" union ");
|
||||
if ( dialect.supportsUnionAll() ) {
|
||||
buf.append("all ");
|
||||
|
@ -577,7 +599,12 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
buf.append( dialect.getSelectClauseNullString( sqlType, getFactory().getTypeConfiguration() ) )
|
||||
.append( " as " );
|
||||
}
|
||||
new ColumnReference( (String) null, selectableMapping ).appendReadExpression( sqlAppender );
|
||||
if ( selectableMapping.isFormula() ) {
|
||||
buf.append( selectableMapping.getSelectableName() );
|
||||
}
|
||||
else {
|
||||
buf.append( selectableMapping.getSelectionExpression() );
|
||||
}
|
||||
buf.append( ", " );
|
||||
}
|
||||
buf.append( persister.getDiscriminatorSQLValue() )
|
||||
|
@ -585,7 +612,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
.append( subclassTableName );
|
||||
}
|
||||
}
|
||||
return buf.append( " )" ).toString();
|
||||
return buf.append( ")" ).toString();
|
||||
}
|
||||
|
||||
private void collectSelectableOwners(LinkedHashMap<String, Map<String, SelectableMapping>> selectables) {
|
||||
|
@ -602,7 +629,14 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
selectable.getSelectionExpression(),
|
||||
k -> new HashMap<>()
|
||||
);
|
||||
selectableMapping.put( getTableName(), selectable );
|
||||
final String subclassTableName;
|
||||
if ( hasSubclasses() ) {
|
||||
subclassTableName = getRootTableName();
|
||||
}
|
||||
else {
|
||||
subclassTableName = getTableName();
|
||||
}
|
||||
selectableMapping.put( subclassTableName, selectable );
|
||||
};
|
||||
getIdentifierMapping().forEachSelectable( selectableConsumer );
|
||||
if ( getVersionMapping() != null ) {
|
||||
|
@ -613,6 +647,11 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
|||
for ( int i = 0; i < size; i++ ) {
|
||||
attributeMappings.get( i ).forEachSelectable( selectableConsumer );
|
||||
}
|
||||
for ( EntityMappingType subMappingType : getSubMappingTypes() ) {
|
||||
if ( !subMappingType.isAbstract() ) {
|
||||
( (UnionSubclassEntityPersister) subMappingType ).collectSelectableOwners( selectables );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,8 @@ public class SqmCreationHelper {
|
|||
);
|
||||
}
|
||||
NavigablePath navigablePath = lhs.getNavigablePath();
|
||||
if ( lhs.getReferencedPathSource() instanceof PluralPersistentAttribute<?, ?, ?> ) {
|
||||
if ( lhs.getReferencedPathSource() instanceof PluralPersistentAttribute<?, ?, ?>
|
||||
&& CollectionPart.Nature.fromName( subNavigable ) == null ) {
|
||||
navigablePath = navigablePath.append( CollectionPart.Nature.ELEMENT.getName() );
|
||||
}
|
||||
return buildSubNavigablePath( navigablePath, subNavigable, alias );
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,10 +14,13 @@ import org.hibernate.metamodel.MappingMetamodel;
|
|||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||
import org.hibernate.metamodel.mapping.MappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.query.sqm.tree.from.SqmFrom;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
|
@ -45,25 +48,37 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
|||
public static <T> BasicValuedPathInterpretation<T> from(
|
||||
SqmBasicValuedSimplePath<T> sqmPath,
|
||||
SqlAstCreationState sqlAstCreationState,
|
||||
SemanticQueryWalker sqmWalker,
|
||||
SemanticQueryWalker<?> sqmWalker,
|
||||
boolean jpaQueryComplianceEnabled,
|
||||
Clause currentClause) {
|
||||
final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess();
|
||||
final TableGroup tableGroup = fromClauseAccess.getTableGroup( sqmPath.getNavigablePath().getParent() );
|
||||
|
||||
final SqmPath<?> lhs = sqmPath.getLhs();
|
||||
EntityMappingType treatTarget = null;
|
||||
if ( jpaQueryComplianceEnabled ) {
|
||||
if ( sqmPath.getLhs() instanceof SqmTreatedPath ) {
|
||||
final EntityDomainType treatTargetDomainType = ( (SqmTreatedPath) sqmPath.getLhs() ).getTreatTarget();
|
||||
final ModelPartContainer modelPartContainer;
|
||||
if ( lhs instanceof SqmTreatedPath<?, ?> ) {
|
||||
final EntityDomainType<?> treatTargetDomainType = ( (SqmTreatedPath<?, ?>) lhs ).getTreatTarget();
|
||||
|
||||
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
treatTarget = mappingMetamodel.findEntityDescriptor( treatTargetDomainType.getHibernateEntityName() );
|
||||
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
final EntityPersister treatEntityDescriptor = mappingMetamodel.findEntityDescriptor( treatTargetDomainType.getHibernateEntityName() );
|
||||
final MappingType tableGroupMappingType = tableGroup.getModelPart().getPartMappingType();
|
||||
if ( tableGroupMappingType instanceof EntityMappingType
|
||||
&& treatEntityDescriptor.isTypeOrSuperType( (EntityMappingType) tableGroupMappingType ) ) {
|
||||
modelPartContainer = tableGroup.getModelPart();
|
||||
treatTarget = treatEntityDescriptor;
|
||||
}
|
||||
else if ( sqmPath.getLhs().getNodeType() instanceof EntityDomainType ) {
|
||||
final EntityDomainType entityDomainType = (EntityDomainType) sqmPath.getLhs().getNodeType();
|
||||
else {
|
||||
modelPartContainer = treatEntityDescriptor;
|
||||
}
|
||||
}
|
||||
else {
|
||||
modelPartContainer = tableGroup.getModelPart();
|
||||
if ( jpaQueryComplianceEnabled && lhs.getNodeType() instanceof EntityDomainType<?> ) {
|
||||
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) lhs.getNodeType();
|
||||
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getRuntimeMetamodels()
|
||||
|
@ -72,20 +87,19 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
|||
}
|
||||
}
|
||||
|
||||
final ModelPartContainer modelPart = tableGroup.getModelPart();
|
||||
final BasicValuedModelPart mapping;
|
||||
// In the select, group by, order by and having clause we have to make sure we render the column of the target table,
|
||||
// never the FK column, if the lhs is a SqmFrom i.e. something explicitly queried/joined.
|
||||
if ( ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
|
||||
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
|
||||
&& modelPart.getPartMappingType() instanceof ManagedMappingType ) {
|
||||
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPart.getPartMappingType() ).findSubPart(
|
||||
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType ) {
|
||||
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
|
||||
sqmPath.getReferencedPathSource().getPathName(),
|
||||
treatTarget
|
||||
);
|
||||
}
|
||||
else {
|
||||
mapping = (BasicValuedModelPart) modelPart.findSubPart(
|
||||
mapping = (BasicValuedModelPart) modelPartContainer.findSubPart(
|
||||
sqmPath.getReferencedPathSource().getPathName(),
|
||||
treatTarget
|
||||
);
|
||||
|
@ -153,6 +167,11 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
|||
return columnReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnReference getColumnReference() {
|
||||
return columnReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
columnReference.accept( sqlTreeWalker );
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Map;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
|
@ -19,11 +20,11 @@ import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
|||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.FetchableContainer;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
|
@ -35,6 +36,7 @@ public class SqlAstQueryPartProcessingStateImpl
|
|||
implements SqlAstQueryPartProcessingState {
|
||||
|
||||
private final QueryPart queryPart;
|
||||
private final Map<TableGroup, Map<EntityDomainType<?>, Boolean>> treatRegistrations = new HashMap<>();
|
||||
private final boolean deduplicateSelectionItems;
|
||||
private FetchParent nestingFetchParent;
|
||||
|
||||
|
@ -74,6 +76,30 @@ public class SqlAstQueryPartProcessingStateImpl
|
|||
return queryPart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTreat(TableGroup tableGroup, EntityDomainType<?> treatType) {
|
||||
treatRegistrations.computeIfAbsent( tableGroup, tg -> new HashMap<>() ).put( treatType, Boolean.FALSE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTreatUsage(TableGroup tableGroup, EntityDomainType<?> treatType) {
|
||||
final Map<EntityDomainType<?>, Boolean> treatUses = treatRegistrations.get( tableGroup );
|
||||
if ( treatUses == null ) {
|
||||
final SqlAstProcessingState parentState = getParentState();
|
||||
if ( parentState instanceof SqlAstQueryPartProcessingState ) {
|
||||
( (SqlAstQueryPartProcessingState) parentState ).registerTreatUsage( tableGroup, treatType );
|
||||
}
|
||||
}
|
||||
else {
|
||||
treatUses.put( treatType, Boolean.TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations() {
|
||||
return treatRegistrations;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SqlExpressionResolver
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.query.sqm.tree.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -502,6 +503,11 @@ public class SqmPolymorphicRootDescriptor<T> implements EntityDomainType<T> {
|
|||
throw new UnsupportedOperationException( );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends EntityDomainType<? extends T>> getSubTypes() {
|
||||
throw new UnsupportedOperationException( );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSubType(ManagedDomainType subType) {
|
||||
throw new UnsupportedOperationException( );
|
||||
|
|
|
@ -57,6 +57,10 @@ public class InFragment {
|
|||
return setColumn( this.columnName );
|
||||
}
|
||||
|
||||
public List<Object> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public String toFragmentString() {
|
||||
if ( values.size() == 0 ) {
|
||||
return "1=2";
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast.spi;
|
||||
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.persister.entity.EntityNameUse;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
||||
/**
|
||||
* Access to stuff used while creating a SQL AST
|
||||
|
@ -28,4 +31,15 @@ public interface SqlAstCreationState {
|
|||
LoadQueryInfluencers getLoadQueryInfluencers();
|
||||
|
||||
void registerLockMode(String identificationVariable, LockMode explicitLockMode);
|
||||
|
||||
/**
|
||||
* This callback is for handling of filters and is necessary to allow correct treat optimizations.
|
||||
*/
|
||||
@Internal
|
||||
default void registerEntityNameUsage(
|
||||
TableGroup tableGroup,
|
||||
EntityNameUse entityNameUse,
|
||||
String hibernateEntityName) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,15 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast.spi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||
|
||||
/**
|
||||
* SqlAstProcessingState specialization for
|
||||
* SqlAstProcessingState specialization for query parts
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState {
|
||||
|
@ -18,4 +23,13 @@ public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState {
|
|||
* considered in-flight as it is probably still being built.
|
||||
*/
|
||||
QueryPart getInflightQueryPart();
|
||||
|
||||
void registerTreat(TableGroup tableGroup, EntityDomainType<?> treatType);
|
||||
|
||||
void registerTreatUsage(TableGroup tableGroup, EntityDomainType<?> treatType);
|
||||
|
||||
/**
|
||||
* The treat registrations. The boolean indicates whether the treat is used in the query part.
|
||||
*/
|
||||
Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations();
|
||||
}
|
||||
|
|
|
@ -48,6 +48,10 @@ public class CorrelatedTableGroup extends AbstractTableGroup {
|
|||
this.joinPredicateConsumer = joinPredicateConsumer;
|
||||
}
|
||||
|
||||
public TableGroup getCorrelatedTableGroup() {
|
||||
return correlatedTableGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTableGroupJoin(TableGroupJoin join) {
|
||||
assert !getTableGroupJoins().contains( join );
|
||||
|
@ -155,8 +159,4 @@ public class CorrelatedTableGroup extends AbstractTableGroup {
|
|||
public Consumer<Predicate> getJoinPredicateConsumer() {
|
||||
return joinPredicateConsumer;
|
||||
}
|
||||
|
||||
public TableGroup getCorrelatedTableGroup(){
|
||||
return correlatedTableGroup;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast.tree.from;
|
||||
|
||||
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
|
||||
/**
|
||||
|
@ -18,4 +19,15 @@ public interface PluralTableGroup extends TableGroup {
|
|||
TableGroup getElementTableGroup();
|
||||
|
||||
TableGroup getIndexTableGroup();
|
||||
|
||||
default TableGroup getTableGroup(CollectionPart.Nature nature) {
|
||||
switch ( nature ) {
|
||||
case ELEMENT:
|
||||
return getElementTableGroup();
|
||||
case INDEX:
|
||||
return getIndexTableGroup();
|
||||
}
|
||||
|
||||
throw new IllegalStateException( "Could not find table group for: " + nature );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.ArrayList;
|
|||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -446,11 +447,12 @@ public class EntityMetamodel implements Serializable {
|
|||
hasOwnedCollections = foundOwnedCollection;
|
||||
mutablePropertiesIndexes = mutableIndexes;
|
||||
|
||||
final Set<String> subclassEntityNamesLocal = new HashSet<>();
|
||||
// Need deterministic ordering
|
||||
final Set<String> subclassEntityNamesLocal = new LinkedHashSet<>();
|
||||
subclassEntityNamesLocal.add( name );
|
||||
for ( Subclass subclass : persistentClass.getSubclasses() ) {
|
||||
subclassEntityNamesLocal.add( subclass.getEntityName() );
|
||||
}
|
||||
subclassEntityNamesLocal.add( name );
|
||||
subclassEntityNames = toSmallSet( subclassEntityNamesLocal );
|
||||
|
||||
HashMap<Class<?>, String> entityNameByInheritanceClassMapLocal = new HashMap<>();
|
||||
|
|
|
@ -8,6 +8,9 @@ package org.hibernate.orm.test.inheritance;
|
|||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.internal.util.ExceptionHelper;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
|
@ -27,23 +30,24 @@ import jakarta.persistence.criteria.ParameterExpression;
|
|||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
@TestForIssue(jiraKey = "HHH-14103")
|
||||
@JiraKey("HHH-14103")
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
TransientOverrideAsPersistentJoined.Employee.class,
|
||||
|
@ -114,25 +118,24 @@ public class TransientOverrideAsPersistentJoined {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(jiraKey = "HHH-12981")
|
||||
@JiraKey("HHH-12981")
|
||||
public void testQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class )
|
||||
.setParameter( "title", "Senior Editor" )
|
||||
.getSingleResult();
|
||||
assertThat( editor, instanceOf( Editor.class ) );
|
||||
|
||||
final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class )
|
||||
.setParameter( "title", "Writing" )
|
||||
.getSingleResult();
|
||||
assertThat( writer, instanceOf( Writer.class ) );
|
||||
assertNotNull( ( (Writer) writer ).getGroup() );
|
||||
assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup().getName() );
|
||||
try {
|
||||
session.createQuery( "from Employee where title=:title", Employee.class );
|
||||
fail( "Expected exception!" );
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(
|
||||
ExceptionHelper.getRootCause( e ).getMessage(),
|
||||
containsString( "due to the attribute being declared in multiple sub types" )
|
||||
);
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(jiraKey = "HHH-12981")
|
||||
@JiraKey("HHH-12981")
|
||||
public void testQueryByRootClassAndOverridenPropertyTreat(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
final Employee editor = session.createQuery(
|
||||
|
@ -172,7 +175,7 @@ public class TransientOverrideAsPersistentJoined {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected(jiraKey = "HHH-12981")
|
||||
@JiraKey("HHH-12981")
|
||||
public void testCriteriaQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
|
||||
|
|
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.jpa.criteria;
|
||||
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
EntityUseJoinedSubclassOptimizationTest.Thing.class,
|
||||
EntityUseJoinedSubclassOptimizationTest.Building.class,
|
||||
EntityUseJoinedSubclassOptimizationTest.House.class,
|
||||
EntityUseJoinedSubclassOptimizationTest.Skyscraper.class,
|
||||
EntityUseJoinedSubclassOptimizationTest.Vehicle.class,
|
||||
EntityUseJoinedSubclassOptimizationTest.Car.class,
|
||||
EntityUseJoinedSubclassOptimizationTest.Airplane.class
|
||||
}
|
||||
)
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
// Run only on H2 to avoid dealing with SQL dialect differences
|
||||
@RequiresDialect( H2Dialect.class )
|
||||
public class EntityUseJoinedSubclassOptimizationTest {
|
||||
|
||||
@Test
|
||||
public void testEqTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) = House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
// We need to join all tables because the EntityResult will create fetches for all subtypes.
|
||||
// We could optimize this by making use of tableGroupEntityNameUses in BaseSqmToSqlAstConverter#visitFetches,
|
||||
// but for that to be safe, we need to call BaseSqmToSqlAstConverter#visitSelectClause last
|
||||
// and make sure the select clause contains no treat expressions, as that would affect tableGroupEntityNameUses
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end=2",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqSuperTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select 1 from Thing t where type(t) = Building" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"1 " +
|
||||
"from Thing t1_0 " +
|
||||
"join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"left join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"end=1",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqTypeRestrictionSelectId(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select t.id from Thing t where type(t) = House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
// If we use select items directly, we only use the entity name on which the attribute was declared,
|
||||
// so we can cut down the joined tables further
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id " +
|
||||
"from Thing t1_0 " +
|
||||
"join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"end=2",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) <> House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
// We need to join all tables because the EntityDomainResult will create fetches for all subtypes
|
||||
// But actually, to know if a row is of type "House" or not, we need to join that table anyway
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"left join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"left join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end!=2",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) in (House, Car)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"left join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"left join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end in (2,5)",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTypeCommonSuperTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) in (House, Skyscraper)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"left join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end in (2,3)",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotInTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) not in (House, Car)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"left join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"left join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end not in (2,5)",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPath(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as House).familyName is not null" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
// We need to join all tables because the EntityResult will create fetches for all subtypes.
|
||||
// See #testEqTypeRestriction() for further explanation
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"t1_2.familyName is not null",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPathSharedColumn(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as Skyscraper).doors is not null" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end," +
|
||||
"t1_6.seats," +
|
||||
"t1_1.nr," +
|
||||
"t1_5.doors," +
|
||||
"t1_2.familyName," +
|
||||
"t1_3.architectName," +
|
||||
"t1_3.doors," +
|
||||
"t1_4.name " +
|
||||
"from Thing t1_0 " +
|
||||
"join Building t1_1 on t1_0.id=t1_1.id " +
|
||||
"left join House t1_2 on t1_0.id=t1_2.id " +
|
||||
"join Skyscraper t1_3 on t1_0.id=t1_3.id " +
|
||||
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
|
||||
"left join Car t1_5 on t1_0.id=t1_5.id " +
|
||||
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
|
||||
"where " +
|
||||
"case when case " +
|
||||
"when t1_2.id is not null then 2 " +
|
||||
"when t1_3.id is not null then 3 " +
|
||||
"when t1_5.id is not null then 5 " +
|
||||
"when t1_6.id is not null then 6 " +
|
||||
"when t1_1.id is not null then 1 " +
|
||||
"when t1_4.id is not null then 4 " +
|
||||
"end=3 then t1_3.doors end is not null",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryChildUseParent(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select t.nr from Skyscraper t" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"s1_1.nr " +
|
||||
"from Skyscraper s1_0 " +
|
||||
"join Building s1_1 on s1_0.id=s1_1.id",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "Thing")
|
||||
@Inheritance(strategy = InheritanceType.JOINED)
|
||||
public static abstract class Thing {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public Thing() {
|
||||
}
|
||||
|
||||
public Thing(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Building")
|
||||
public static class Building extends Thing {
|
||||
|
||||
private Integer nr;
|
||||
|
||||
public Building() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "House")
|
||||
public static class House extends Building {
|
||||
|
||||
private String familyName;
|
||||
|
||||
public House() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Skyscraper")
|
||||
public static class Skyscraper extends Building {
|
||||
|
||||
private String architectName;
|
||||
private Integer doors;
|
||||
|
||||
public Skyscraper() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Vehicle")
|
||||
public static class Vehicle extends Thing {
|
||||
|
||||
private String name;
|
||||
|
||||
public Vehicle() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Car")
|
||||
public static class Car extends Vehicle {
|
||||
|
||||
private Integer doors;
|
||||
|
||||
public Car() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Airplane")
|
||||
public static class Airplane extends Vehicle {
|
||||
|
||||
private Integer seats;
|
||||
|
||||
public Airplane() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.jpa.criteria;
|
||||
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
EntityUseSingleTableOptimizationTest.Thing.class,
|
||||
EntityUseSingleTableOptimizationTest.Building.class,
|
||||
EntityUseSingleTableOptimizationTest.House.class,
|
||||
EntityUseSingleTableOptimizationTest.Skyscraper.class,
|
||||
EntityUseSingleTableOptimizationTest.Vehicle.class,
|
||||
EntityUseSingleTableOptimizationTest.Car.class,
|
||||
EntityUseSingleTableOptimizationTest.Airplane.class
|
||||
}
|
||||
)
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
// Run only on H2 to avoid dealing with SQL dialect differences
|
||||
@RequiresDialect( H2Dialect.class )
|
||||
public class EntityUseSingleTableOptimizationTest {
|
||||
|
||||
@Test
|
||||
public void testEqTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) = House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE='House'",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqSuperTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select 1 from Thing t where type(t) = Building" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"1 " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE='Building'",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqTypeRestrictionSelectId(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select t.id from Thing t where type(t) = House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE='House'",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) <> House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE!='House'",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) in (House, Car)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE in ('House','Car')",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTypeCommonSuperTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) in (House, Skyscraper)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE in ('House','Skyscraper')",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotInTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) not in (House, Car)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE not in ('House','Car')",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPath(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as House).familyName is not null" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (select * from Thing t where t.DTYPE='House') t1_0 " +
|
||||
"where " +
|
||||
"t1_0.familyName is not null",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPathSharedColumn(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as Skyscraper).doors is not null" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (select * from Thing t where t.DTYPE='Skyscraper') t1_0 " +
|
||||
"where " +
|
||||
"case when t1_0.DTYPE='Skyscraper' then t1_0.doors end is not null",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPathInDisjunction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as House).familyName is not null or t.id > 0" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.familyName is not null or t1_0.id>0",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeRestrictionInDisjunction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) = House or t.id > 0" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.DTYPE," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from Thing t1_0 " +
|
||||
"where " +
|
||||
"t1_0.DTYPE='House' or t1_0.id>0",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryChildUseParent(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select t.nr from Skyscraper t" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"s1_0.nr " +
|
||||
"from Thing s1_0 " +
|
||||
"where s1_0.DTYPE='Skyscraper'",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "Thing")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
public static abstract class Thing {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public Thing() {
|
||||
}
|
||||
|
||||
public Thing(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Building")
|
||||
public static class Building extends Thing {
|
||||
|
||||
private Integer nr;
|
||||
|
||||
public Building() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "House")
|
||||
public static class House extends Building {
|
||||
|
||||
private String familyName;
|
||||
|
||||
public House() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Skyscraper")
|
||||
public static class Skyscraper extends Building {
|
||||
|
||||
private String architectName;
|
||||
private Integer doors;
|
||||
|
||||
public Skyscraper() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Vehicle")
|
||||
public static class Vehicle extends Thing {
|
||||
|
||||
private String name;
|
||||
|
||||
public Vehicle() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Car")
|
||||
public static class Car extends Vehicle {
|
||||
|
||||
private Integer doors;
|
||||
|
||||
public Car() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Airplane")
|
||||
public static class Airplane extends Vehicle {
|
||||
|
||||
private Integer seats;
|
||||
|
||||
public Airplane() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.jpa.criteria;
|
||||
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
EntityUseUnionSubclassOptimizationTest.Thing.class,
|
||||
EntityUseUnionSubclassOptimizationTest.Building.class,
|
||||
EntityUseUnionSubclassOptimizationTest.House.class,
|
||||
EntityUseUnionSubclassOptimizationTest.Skyscraper.class,
|
||||
EntityUseUnionSubclassOptimizationTest.Vehicle.class,
|
||||
EntityUseUnionSubclassOptimizationTest.Car.class,
|
||||
EntityUseUnionSubclassOptimizationTest.Airplane.class
|
||||
}
|
||||
)
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
// Run only on H2 to avoid dealing with SQL dialect differences
|
||||
@RequiresDialect( H2Dialect.class )
|
||||
public class EntityUseUnionSubclassOptimizationTest {
|
||||
|
||||
@Test
|
||||
public void testEqTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) = House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_=2",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqSuperTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select 1 from Thing t where type(t) = Building" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"1 " +
|
||||
"from (" +
|
||||
"select id, nr, null as familyName, null as architectName, null as doors, 1 as clazz_ from Building" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_=1",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqTypeRestrictionSelectId(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select t.id from Thing t where type(t) = House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id " +
|
||||
"from (" +
|
||||
"select id, nr, familyName, 2 as clazz_ from House" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_=2",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) <> House" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, null as nr, null as familyName, null as architectName, null as doors, name, seats, 6 as clazz_ from Airplane " +
|
||||
"union all " +
|
||||
"select id, nr, null as familyName, null as architectName, null as doors, null as name, null as seats, 1 as clazz_ from Building " +
|
||||
"union all " +
|
||||
"select id, null as nr, null as familyName, null as architectName, doors, name, null as seats, 5 as clazz_ from Car " +
|
||||
"union all " +
|
||||
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper " +
|
||||
"union all " +
|
||||
"select id, null as nr, null as familyName, null as architectName, null as doors, name, null as seats, 4 as clazz_ from Vehicle" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_!=2",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) in (House, Car)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, null as nr, null as familyName, null as architectName, doors, name, null as seats, 5 as clazz_ from Car " +
|
||||
"union all " +
|
||||
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_ in (2,5)",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTypeCommonSuperTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) in (House, Skyscraper)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House " +
|
||||
"union all " +
|
||||
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_ in (2,3)",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotInTypeRestriction(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where type(t) not in (House, Car)" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, null as nr, null as familyName, null as architectName, null as doors, name, seats, 6 as clazz_ from Airplane " +
|
||||
"union all " +
|
||||
"select id, nr, null as familyName, null as architectName, null as doors, null as name, null as seats, 1 as clazz_ from Building " +
|
||||
"union all " +
|
||||
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper " +
|
||||
"union all " +
|
||||
"select id, null as nr, null as familyName, null as architectName, null as doors, name, null as seats, 4 as clazz_ from Vehicle" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.clazz_ not in (2,5)",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPath(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as House).familyName is not null" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"t1_0.familyName is not null",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatPathSharedColumn(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "from Thing t where treat(t as Skyscraper).doors is not null" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"t1_0.id," +
|
||||
"t1_0.clazz_," +
|
||||
"t1_0.seats," +
|
||||
"t1_0.nr," +
|
||||
"t1_0.doors," +
|
||||
"t1_0.familyName," +
|
||||
"t1_0.architectName," +
|
||||
"t1_0.name " +
|
||||
"from (" +
|
||||
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper" +
|
||||
") t1_0 " +
|
||||
"where " +
|
||||
"case when t1_0.clazz_=3 then t1_0.doors end is not null",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryChildUseParent(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
sqlStatementInterceptor.clear();
|
||||
entityManager.createSelectionQuery( "select t.nr from Skyscraper t" )
|
||||
.getResultList();
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"s1_0.nr " +
|
||||
"from Skyscraper s1_0",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "Thing")
|
||||
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
|
||||
public static abstract class Thing {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
public Thing() {
|
||||
}
|
||||
|
||||
public Thing(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Building")
|
||||
public static class Building extends Thing {
|
||||
|
||||
private Integer nr;
|
||||
|
||||
public Building() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "House")
|
||||
public static class House extends Building {
|
||||
|
||||
private String familyName;
|
||||
|
||||
public House() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Skyscraper")
|
||||
public static class Skyscraper extends Building {
|
||||
|
||||
private String architectName;
|
||||
private Integer doors;
|
||||
|
||||
public Skyscraper() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Vehicle")
|
||||
public static class Vehicle extends Thing {
|
||||
|
||||
private String name;
|
||||
|
||||
public Vehicle() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Car")
|
||||
public static class Car extends Vehicle {
|
||||
|
||||
private Integer doors;
|
||||
|
||||
public Car() {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Airplane")
|
||||
public static class Airplane extends Vehicle {
|
||||
|
||||
private Integer seats;
|
||||
|
||||
public Airplane() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.jpa.criteria;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.From;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
TreatDisjunctionTest.PAccountDirectory.class,
|
||||
TreatDisjunctionTest.PLDAPDirectory.class
|
||||
}
|
||||
)
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class TreatDisjunctionTest {
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-15726")
|
||||
public void testQuery(SessionFactoryScope scope) {
|
||||
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
PAccountDirectory base = new PAccountDirectory();
|
||||
base.setActive( true );
|
||||
entityManager.persist( base );
|
||||
|
||||
PLDAPDirectory sub = new PLDAPDirectory();
|
||||
sub.setActive( false );
|
||||
sub.setOpenldap( true );
|
||||
entityManager.persist( sub );
|
||||
entityManager.flush();
|
||||
entityManager.clear();
|
||||
|
||||
sqlStatementInterceptor.clear();
|
||||
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<PAccountDirectory> query = cb.createQuery( PAccountDirectory.class );
|
||||
Root<PAccountDirectory> root = query.from( PAccountDirectory.class );
|
||||
|
||||
From<?, PLDAPDirectory> ldap = cb.treat( root, PLDAPDirectory.class );
|
||||
Predicate exp = cb.or(
|
||||
cb.equal( root.get( "active" ), true ),
|
||||
cb.equal( ldap.get( "openldap" ), true )
|
||||
);
|
||||
|
||||
List<PAccountDirectory> directories = entityManager.createQuery( query.select( root ).where( exp ) )
|
||||
.getResultList();
|
||||
assertThat( directories, hasSize( 2 ) );
|
||||
|
||||
sqlStatementInterceptor.assertExecutedCount( 1 );
|
||||
assertEquals(
|
||||
"select " +
|
||||
"p1_0.id," +
|
||||
"p1_0.DTYPE," +
|
||||
"p1_0.active," +
|
||||
"p1_0.openldap " +
|
||||
"from PAccountDirectory p1_0 " +
|
||||
"where p1_0.active=? " +
|
||||
"or p1_0.openldap=?",
|
||||
sqlStatementInterceptor.getSqlQueries().get( 0 )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
public static abstract class BaseEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
public BaseEntity() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "PAccountDirectory")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
public static class PAccountDirectory extends BaseEntity {
|
||||
@Column(nullable = false)
|
||||
private boolean active;
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "PLDAPDirectory")
|
||||
public static class PLDAPDirectory extends PAccountDirectory {
|
||||
@Column(nullable = true)
|
||||
private boolean openldap;
|
||||
|
||||
public boolean isOpenldap() {
|
||||
return openldap;
|
||||
}
|
||||
|
||||
public void setOpenldap(boolean openldap) {
|
||||
this.openldap = openldap;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -113,10 +113,10 @@ public class AbstractPathImplTest extends AbstractMetamodelSpecificTest {
|
|||
Root<Thing> thingRoot = criteria.from( Thing.class );
|
||||
|
||||
criteria.select( thingRoot );
|
||||
assertEquals( em.createQuery( criteria ).getResultList().size(), 3);
|
||||
assertEquals( 3, em.createQuery( criteria ).getResultList().size() );
|
||||
|
||||
criteria.where( criteriaBuilder.equal( thingRoot.type(), criteriaBuilder.literal( Thing.class ) ) );
|
||||
assertEquals( em.createQuery( criteria ).getResultList().size(), 2 );
|
||||
assertEquals( 2, em.createQuery( criteria ).getResultList().size() );
|
||||
}
|
||||
finally {
|
||||
em.close();
|
||||
|
|
|
@ -13,7 +13,6 @@ import jakarta.persistence.Id;
|
|||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
|
||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
|
@ -37,7 +36,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
public class JoinedSubclassDuplicateFieldsWithTreatTest {
|
||||
|
||||
@Test
|
||||
@FailureExpected( jiraKey = "HHH-11686" )
|
||||
public void queryConstrainedSubclass(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
Deposit deposit1 = new Deposit();
|
||||
|
|
Loading…
Reference in New Issue