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 );
|
appendSql( UNION_ALL );
|
||||||
searchIndex = unionIndex + UNION_ALL.length();
|
searchIndex = unionIndex + UNION_ALL.length();
|
||||||
}
|
}
|
||||||
append( tableExpression, searchIndex, tableExpression.length() - 2 );
|
append( tableExpression, searchIndex, tableExpression.length() - 1 );
|
||||||
renderLockHint( lockMode );
|
renderLockHint( lockMode );
|
||||||
appendSql( " )" );
|
appendSql( " )" );
|
||||||
|
|
||||||
|
|
|
@ -1093,7 +1093,7 @@ public class BinderHelper {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ownerClass = getSuperPersistentClass( ownerClass );
|
ownerClass = ownerClass.getSuperPersistentClass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new AnnotationException(
|
throw new AnnotationException(
|
||||||
|
@ -1110,24 +1110,9 @@ public class BinderHelper {
|
||||||
if ( ownerClass.getTable() == referencedClass.getTable() ) {
|
if ( ownerClass.getTable() == referencedClass.getTable() ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
referencedClass = getSuperPersistentClass( referencedClass );
|
referencedClass = referencedClass.getSuperPersistentClass();
|
||||||
}
|
}
|
||||||
return false;
|
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 );
|
appendSql( UNION_ALL );
|
||||||
searchIndex = unionIndex + UNION_ALL.length();
|
searchIndex = unionIndex + UNION_ALL.length();
|
||||||
}
|
}
|
||||||
append( tableExpression, searchIndex, tableExpression.length() - 2 );
|
append( tableExpression, searchIndex, tableExpression.length() - 1 );
|
||||||
renderLockHint( lockMode );
|
renderLockHint( lockMode );
|
||||||
appendSql( " )" );
|
appendSql( " )" );
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
import org.hibernate.metamodel.mapping.Restrictable;
|
import org.hibernate.metamodel.mapping.Restrictable;
|
||||||
|
import org.hibernate.persister.entity.EntityNameUse;
|
||||||
import org.hibernate.sql.Template;
|
import org.hibernate.sql.Template;
|
||||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
@ -42,6 +43,11 @@ public class FilterHelper {
|
||||||
private final boolean[] filterAutoAliasFlags;
|
private final boolean[] filterAutoAliasFlags;
|
||||||
private final Map<String, String>[] filterAliasTableMaps;
|
private final Map<String, String>[] filterAliasTableMaps;
|
||||||
private final List<String>[] parameterNames;
|
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
|
* 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 filters The map of defined filters.
|
||||||
* @param factory The session factory
|
* @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();
|
int filterCount = filters.size();
|
||||||
|
|
||||||
filterNames = new String[filterCount];
|
filterNames = new String[filterCount];
|
||||||
|
@ -59,6 +65,7 @@ public class FilterHelper {
|
||||||
filterAutoAliasFlags = new boolean[filterCount];
|
filterAutoAliasFlags = new boolean[filterCount];
|
||||||
filterAliasTableMaps = new Map[filterCount];
|
filterAliasTableMaps = new Map[filterCount];
|
||||||
parameterNames = new List[filterCount];
|
parameterNames = new List[filterCount];
|
||||||
|
this.tableToEntityName = tableToEntityName;
|
||||||
|
|
||||||
filterCount = 0;
|
filterCount = 0;
|
||||||
for ( final FilterConfiguration filter : filters ) {
|
for ( final FilterConfiguration filter : filters ) {
|
||||||
|
@ -145,21 +152,32 @@ public class FilterHelper {
|
||||||
public void applyEnabledFilters(
|
public void applyEnabledFilters(
|
||||||
Consumer<Predicate> predicateConsumer,
|
Consumer<Predicate> predicateConsumer,
|
||||||
FilterAliasGenerator aliasGenerator,
|
FilterAliasGenerator aliasGenerator,
|
||||||
Map<String, Filter> enabledFilters) {
|
Map<String, Filter> enabledFilters,
|
||||||
final FilterPredicate predicate = generateFilterPredicate( aliasGenerator, enabledFilters );
|
TableGroup tableGroup,
|
||||||
|
SqlAstCreationState creationState) {
|
||||||
|
final FilterPredicate predicate = generateFilterPredicate(
|
||||||
|
aliasGenerator,
|
||||||
|
enabledFilters,
|
||||||
|
tableGroup,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
if ( predicate != null ) {
|
if ( predicate != null ) {
|
||||||
predicateConsumer.accept( predicate );
|
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();
|
final FilterPredicate filterPredicate = new FilterPredicate();
|
||||||
|
|
||||||
for ( int i = 0, max = filterNames.length; i < max; i++ ) {
|
for ( int i = 0, max = filterNames.length; i < max; i++ ) {
|
||||||
final String filterName = filterNames[i];
|
final String filterName = filterNames[i];
|
||||||
final FilterImpl enabledFilter = (FilterImpl) enabledFilters.get( filterName );
|
final FilterImpl enabledFilter = (FilterImpl) enabledFilters.get( filterName );
|
||||||
if ( enabledFilter != null ) {
|
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 ) {
|
if ( buffer.length() > 0 ) {
|
||||||
buffer.append( " and " );
|
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];
|
Map<String, String> aliasTableMap = filterAliasTableMaps[filterIndex];
|
||||||
String condition = filterConditions[filterIndex];
|
String condition = filterConditions[filterIndex];
|
||||||
if ( aliasGenerator == null ) {
|
if ( aliasGenerator == null ) {
|
||||||
return StringHelper.replace( condition, FilterImpl.MARKER + ".", "");
|
return StringHelper.replace( condition, FilterImpl.MARKER + ".", "");
|
||||||
}
|
}
|
||||||
if ( filterAutoAliasFlags[filterIndex] ) {
|
if ( filterAutoAliasFlags[filterIndex] ) {
|
||||||
return StringHelper.replace(
|
final String tableName = aliasTableMap.get( null );
|
||||||
|
final String newCondition = StringHelper.replace(
|
||||||
condition,
|
condition,
|
||||||
FilterImpl.MARKER,
|
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 ) ) {
|
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 {
|
else {
|
||||||
for ( Map.Entry<String, String> entry : aliasTableMap.entrySet() ) {
|
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() + "}",
|
"{" + 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;
|
return condition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.loader.ast.internal;
|
package org.hibernate.loader.ast.internal;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import jakarta.persistence.CacheRetrieveMode;
|
import jakarta.persistence.CacheRetrieveMode;
|
||||||
import jakarta.persistence.CacheStoreMode;
|
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.AssociationKey;
|
||||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
import org.hibernate.metamodel.mapping.ModelPart;
|
import org.hibernate.metamodel.mapping.ModelPart;
|
||||||
|
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||||
import org.hibernate.query.spi.Limit;
|
import org.hibernate.query.spi.Limit;
|
||||||
import org.hibernate.spi.NavigablePath;
|
import org.hibernate.spi.NavigablePath;
|
||||||
import org.hibernate.query.ResultListTransformer;
|
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.SqlAstProcessingState;
|
||||||
import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState;
|
import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState;
|
||||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
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.ast.tree.select.QueryPart;
|
||||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||||
import org.hibernate.sql.results.graph.Fetch;
|
import org.hibernate.sql.results.graph.Fetch;
|
||||||
|
@ -103,6 +107,21 @@ public class LoaderSqlAstCreationState
|
||||||
return processingState.getInflightQueryPart();
|
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
|
@Override
|
||||||
public SqlExpressionResolver getSqlExpressionResolver() {
|
public SqlExpressionResolver getSqlExpressionResolver() {
|
||||||
return processingState;
|
return processingState;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import org.hibernate.Internal;
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.Remove;
|
import org.hibernate.Remove;
|
||||||
import org.hibernate.boot.Metadata;
|
import org.hibernate.boot.Metadata;
|
||||||
|
@ -1331,4 +1332,45 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
|
||||||
public List<CheckConstraint> getCheckConstraints() {
|
public List<CheckConstraint> getCheckConstraints() {
|
||||||
return checkConstraints;
|
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.UnsupportedMappingException;
|
||||||
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
|
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
|
||||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.persister.entity.EntityNameUse;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
|
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||||
|
@ -245,6 +246,34 @@ public interface EntityMappingType
|
||||||
return superMappingType.getRootEntityDescriptor();
|
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
|
* 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,
|
* 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 tableGroup The table group to prune subclass tables for
|
||||||
* @param treatedEntityNames The entity names for which path usages were registered
|
* @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) {
|
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.ObjectStreamException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -58,7 +59,7 @@ public abstract class AbstractManagedType<J>
|
||||||
private volatile Map<String, PluralPersistentAttribute<J, ?, ?>> declaredPluralAttributes ;
|
private volatile Map<String, PluralPersistentAttribute<J, ?, ?>> declaredPluralAttributes ;
|
||||||
private volatile Map<String, PersistentAttribute<J, ?>> declaredConcreteGenericAttributes;
|
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(
|
protected AbstractManagedType(
|
||||||
String hibernateTypeName,
|
String hibernateTypeName,
|
||||||
|
@ -87,6 +88,11 @@ public abstract class AbstractManagedType<J>
|
||||||
return superType;
|
return superType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends ManagedDomainType<? extends J>> getSubTypes() {
|
||||||
|
return subTypes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addSubType(ManagedDomainType subType){
|
public void addSubType(ManagedDomainType subType){
|
||||||
subTypes.add( subType );
|
subTypes.add( subType );
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.metamodel.model.domain;
|
package org.hibernate.metamodel.model.domain;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import jakarta.persistence.metamodel.EntityType;
|
import jakarta.persistence.metamodel.EntityType;
|
||||||
|
|
||||||
import org.hibernate.query.sqm.SqmPathSource;
|
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> {
|
public interface EntityDomainType<J> extends IdentifiableDomainType<J>, EntityType<J>, SqmPathSource<J> {
|
||||||
String getHibernateEntityName();
|
String getHibernateEntityName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Collection<? extends EntityDomainType<? extends J>> getSubTypes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.metamodel.model.domain;
|
package org.hibernate.metamodel.model.domain;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.hibernate.graph.spi.SubGraphImplementor;
|
import org.hibernate.graph.spi.SubGraphImplementor;
|
||||||
|
@ -41,6 +42,8 @@ public interface ManagedDomainType<J> extends SqmExpressible<J>, DomainType<J>,
|
||||||
*/
|
*/
|
||||||
ManagedDomainType<? super J> getSuperType();
|
ManagedDomainType<? super J> getSuperType();
|
||||||
|
|
||||||
|
Collection<? extends ManagedDomainType<? extends J>> getSubTypes();
|
||||||
|
|
||||||
void addSubType(ManagedDomainType subType);
|
void addSubType(ManagedDomainType subType);
|
||||||
|
|
||||||
void visitAttributes(Consumer<? super PersistentAttribute<J, ?>> action);
|
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.ObjectStreamException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import jakarta.persistence.metamodel.EntityType;
|
import jakarta.persistence.metamodel.EntityType;
|
||||||
|
|
||||||
import org.hibernate.graph.internal.SubGraphImpl;
|
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.EntityDomainType;
|
||||||
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
|
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
|
||||||
import org.hibernate.metamodel.model.domain.JpaMetamodel;
|
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.PersistentAttribute;
|
||||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||||
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
|
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
|
||||||
|
@ -169,6 +172,12 @@ public class EntityTypeImpl<J>
|
||||||
return super.getSuperType();
|
return super.getSuperType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends EntityDomainType<? extends J>> getSubTypes() {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Collection<? extends EntityDomainType<? extends J>>) super.getSubTypes();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <S extends J> SubGraphImplementor<S> makeSubGraph(Class<S> subType) {
|
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.CollectionTableMapping;
|
||||||
import org.hibernate.persister.collection.mutation.RemoveCoordinator;
|
import org.hibernate.persister.collection.mutation.RemoveCoordinator;
|
||||||
import org.hibernate.persister.collection.mutation.RowMutationOperations;
|
import org.hibernate.persister.collection.mutation.RowMutationOperations;
|
||||||
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.persister.entity.Joinable;
|
import org.hibernate.persister.entity.Joinable;
|
||||||
import org.hibernate.persister.entity.PropertyMapping;
|
import org.hibernate.persister.entity.PropertyMapping;
|
||||||
|
@ -533,7 +534,16 @@ public abstract class AbstractCollectionPersister
|
||||||
filterHelper = null;
|
filterHelper = null;
|
||||||
}
|
}
|
||||||
else {
|
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
|
// Handle any filters applied to this collectionBinding for many-to-many
|
||||||
|
@ -1186,12 +1196,19 @@ public abstract class AbstractCollectionPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 ) {
|
if ( filterHelper != null ) {
|
||||||
filterHelper.applyEnabledFilters(
|
filterHelper.applyEnabledFilters(
|
||||||
predicateConsumer,
|
predicateConsumer,
|
||||||
getFilterAliasGenerator( tableGroup ),
|
getFilterAliasGenerator( tableGroup ),
|
||||||
enabledFilters
|
enabledFilters,
|
||||||
|
tableGroup,
|
||||||
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1200,7 +1217,13 @@ public abstract class AbstractCollectionPersister
|
||||||
public abstract boolean isManyToMany();
|
public abstract boolean isManyToMany();
|
||||||
|
|
||||||
@Override
|
@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 ) {
|
if ( manyToManyFilterHelper == null && manyToManyWhereTemplate == null ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1208,7 +1231,13 @@ public abstract class AbstractCollectionPersister
|
||||||
|
|
||||||
if ( manyToManyFilterHelper != null ) {
|
if ( manyToManyFilterHelper != null ) {
|
||||||
final FilterAliasGenerator aliasGenerator = elementPersister.getFilterAliasGenerator( tableGroup );
|
final FilterAliasGenerator aliasGenerator = elementPersister.getFilterAliasGenerator( tableGroup );
|
||||||
manyToManyFilterHelper.applyEnabledFilters( predicateConsumer, aliasGenerator, enabledFilters );
|
manyToManyFilterHelper.applyEnabledFilters(
|
||||||
|
predicateConsumer,
|
||||||
|
aliasGenerator,
|
||||||
|
enabledFilters,
|
||||||
|
tableGroup,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( manyToManyWhereString != null ) {
|
if ( manyToManyWhereString != null ) {
|
||||||
|
|
|
@ -110,6 +110,7 @@ import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.FilterAliasGenerator;
|
import org.hibernate.internal.FilterAliasGenerator;
|
||||||
import org.hibernate.internal.FilterHelper;
|
import org.hibernate.internal.FilterHelper;
|
||||||
|
import org.hibernate.internal.FilterImpl;
|
||||||
import org.hibernate.internal.util.IndexedConsumer;
|
import org.hibernate.internal.util.IndexedConsumer;
|
||||||
import org.hibernate.internal.util.LazyValue;
|
import org.hibernate.internal.util.LazyValue;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
@ -143,6 +144,7 @@ import org.hibernate.mapping.Column;
|
||||||
import org.hibernate.mapping.Component;
|
import org.hibernate.mapping.Component;
|
||||||
import org.hibernate.mapping.DependantValue;
|
import org.hibernate.mapping.DependantValue;
|
||||||
import org.hibernate.mapping.Formula;
|
import org.hibernate.mapping.Formula;
|
||||||
|
import org.hibernate.mapping.Join;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
import org.hibernate.mapping.Selectable;
|
import org.hibernate.mapping.Selectable;
|
||||||
|
@ -358,6 +360,7 @@ public abstract class AbstractEntityPersister
|
||||||
private final String[][] propertyColumnWriters;
|
private final String[][] propertyColumnWriters;
|
||||||
private final boolean[][] propertyColumnUpdateable;
|
private final boolean[][] propertyColumnUpdateable;
|
||||||
private final boolean[][] propertyColumnInsertable;
|
private final boolean[][] propertyColumnInsertable;
|
||||||
|
private final Set<String> sharedColumnNames;
|
||||||
|
|
||||||
private final List<Integer> lobProperties;
|
private final List<Integer> lobProperties;
|
||||||
|
|
||||||
|
@ -434,6 +437,7 @@ public abstract class AbstractEntityPersister
|
||||||
|
|
||||||
protected ReflectionOptimizer.AccessOptimizer accessOptimizer;
|
protected ReflectionOptimizer.AccessOptimizer accessOptimizer;
|
||||||
|
|
||||||
|
protected final String[] fullDiscriminatorSQLValues;
|
||||||
private final Object[] fullDiscriminatorValues;
|
private final Object[] fullDiscriminatorValues;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -599,6 +603,7 @@ public abstract class AbstractEntityPersister
|
||||||
propertyColumnWriters = new String[hydrateSpan][];
|
propertyColumnWriters = new String[hydrateSpan][];
|
||||||
propertyColumnUpdateable = new boolean[hydrateSpan][];
|
propertyColumnUpdateable = new boolean[hydrateSpan][];
|
||||||
propertyColumnInsertable = new boolean[hydrateSpan][];
|
propertyColumnInsertable = new boolean[hydrateSpan][];
|
||||||
|
sharedColumnNames = new HashSet<>();
|
||||||
|
|
||||||
final HashSet<Property> thisClassProperties = new HashSet<>();
|
final HashSet<Property> thisClassProperties = new HashSet<>();
|
||||||
final ArrayList<String> lazyNames = new ArrayList<>();
|
final ArrayList<String> lazyNames = new ArrayList<>();
|
||||||
|
@ -736,6 +741,10 @@ public abstract class AbstractEntityPersister
|
||||||
typeConfiguration,
|
typeConfiguration,
|
||||||
functionRegistry
|
functionRegistry
|
||||||
);
|
);
|
||||||
|
if ( isDefinedBySubclass && persistentClass.isDefinedOnMultipleSubclasses( column )
|
||||||
|
|| !isDefinedBySubclass && persistentClass.hasSubclasses() ) {
|
||||||
|
sharedColumnNames.add( colName );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
propColumns.add( cols );
|
propColumns.add( cols );
|
||||||
|
@ -772,7 +781,7 @@ public abstract class AbstractEntityPersister
|
||||||
|
|
||||||
// Handle any filters applied to the class level
|
// Handle any filters applied to the class level
|
||||||
filterHelper = isNotEmpty( persistentClass.getFilters() )
|
filterHelper = isNotEmpty( persistentClass.getFilters() )
|
||||||
? new FilterHelper( persistentClass.getFilters(), factory )
|
? new FilterHelper( persistentClass.getFilters(), getEntityNameByTableNameMap( persistentClass ), factory )
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
useReferenceCacheEntries = shouldUseReferenceCacheEntries( creationContext.getSessionFactoryOptions() );
|
useReferenceCacheEntries = shouldUseReferenceCacheEntries( creationContext.getSessionFactoryOptions() );
|
||||||
|
@ -782,12 +791,12 @@ public abstract class AbstractEntityPersister
|
||||||
&& shouldInvalidateCache( persistentClass, creationContext );
|
&& shouldInvalidateCache( persistentClass, creationContext );
|
||||||
|
|
||||||
final List<Object> values = new ArrayList<>();
|
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 ( persistentClass.isPolymorphic() && persistentClass.getDiscriminator() != null ) {
|
||||||
if ( !getEntityMetamodel().isAbstract() ) {
|
if ( !getEntityMetamodel().isAbstract() ) {
|
||||||
values.add( DiscriminatorHelper.getDiscriminatorValue( persistentClass ) );
|
values.add( DiscriminatorHelper.getDiscriminatorValue( persistentClass ) );
|
||||||
// sqlValues.add( DiscriminatorHelper.getDiscriminatorSQLValue( persistentClass, dialect, factory ) );
|
sqlValues.add( DiscriminatorHelper.getDiscriminatorSQLValue( persistentClass, dialect ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Subclass> subclasses = persistentClass.getSubclasses();
|
final List<Subclass> subclasses = persistentClass.getSubclasses();
|
||||||
|
@ -796,15 +805,34 @@ public abstract class AbstractEntityPersister
|
||||||
//copy/paste from EntityMetamodel:
|
//copy/paste from EntityMetamodel:
|
||||||
if ( !isAbstract( subclass ) ) {
|
if ( !isAbstract( subclass ) ) {
|
||||||
values.add( DiscriminatorHelper.getDiscriminatorValue( 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 );
|
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) {
|
private MultiIdEntityLoader<Object> buildMultiIdLoader(PersistentClass persistentClass) {
|
||||||
if ( persistentClass.getIdentifier() instanceof BasicValue
|
if ( persistentClass.getIdentifier() instanceof BasicValue
|
||||||
&& MultiKeyLoadHelper.supportsSqlArrayType( factory.getServiceRegistry().getService( JdbcServices.class ).getDialect() ) ) {
|
&& MultiKeyLoadHelper.supportsSqlArrayType( factory.getServiceRegistry().getService( JdbcServices.class ).getDialect() ) ) {
|
||||||
|
@ -945,6 +973,10 @@ public abstract class AbstractEntityPersister
|
||||||
return entityMetamodel.getSubclassEntityNames().contains( entityName );
|
return entityMetamodel.getSubclassEntityNames().contains( entityName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSharedColumn(String columnExpression) {
|
||||||
|
return sharedColumnNames.contains( columnExpression );
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean[] getTableHasColumns() {
|
protected boolean[] getTableHasColumns() {
|
||||||
return tableHasColumns;
|
return tableHasColumns;
|
||||||
}
|
}
|
||||||
|
@ -3007,7 +3039,13 @@ public abstract class AbstractEntityPersister
|
||||||
final FilterAliasGenerator filterAliasGenerator = useQualifier && tableGroup != null
|
final FilterAliasGenerator filterAliasGenerator = useQualifier && tableGroup != null
|
||||||
? getFilterAliasGenerator( tableGroup )
|
? getFilterAliasGenerator( tableGroup )
|
||||||
: null;
|
: 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
|
// Span of the tables directly mapped by this entity and super-classes, if any
|
||||||
private final int coreTableSpan;
|
private final int coreTableSpan;
|
||||||
|
private final int subclassCoreTableSpan;
|
||||||
// only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
|
// only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
|
||||||
private final boolean[] isNullableTable;
|
private final boolean[] isNullableTable;
|
||||||
private final boolean[] isInverseTable;
|
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
|
//Span of the tableNames directly mapped by this entity and super-classes, if any
|
||||||
coreTableSpan = tableNames.size();
|
coreTableSpan = tableNames.size();
|
||||||
|
subclassCoreTableSpan = persistentClass.getSubclassTableClosure().size();
|
||||||
tableSpan = persistentClass.getJoinClosureSpan() + coreTableSpan;
|
tableSpan = persistentClass.getJoinClosureSpan() + coreTableSpan;
|
||||||
|
|
||||||
isNullableTable = new boolean[tableSpan];
|
isNullableTable = new boolean[tableSpan];
|
||||||
|
@ -1302,57 +1304,104 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
|
public void pruneForSubclasses(TableGroup tableGroup, Map<String, EntityNameUse> entityNameUses) {
|
||||||
final Set<TableReference> retainedTableReferences = new HashSet<>( treatedEntityNames.size() );
|
final Set<TableReference> retainedTableReferences = new HashSet<>( entityNameUses.size() );
|
||||||
final Set<String> sharedSuperclassTables = new HashSet<>();
|
|
||||||
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
|
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
|
||||||
|
// We can only do this optimization if the table group reports canUseInnerJoins or isRealTableGroup,
|
||||||
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,
|
|
||||||
// because the switch for table reference joins to INNER must be cardinality preserving.
|
// 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
|
// 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
|
// 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] );
|
final TableReferenceJoin[] oldJoins = tableReferenceJoins.toArray( new TableReferenceJoin[0] );
|
||||||
tableReferenceJoins.clear();
|
tableReferenceJoins.clear();
|
||||||
for ( TableReferenceJoin oldJoin : oldJoins ) {
|
for ( TableReferenceJoin oldJoin : oldJoins ) {
|
||||||
final NamedTableReference joinedTableReference = oldJoin.getJoinedTableReference();
|
final NamedTableReference joinedTableReference = oldJoin.getJoinedTableReference();
|
||||||
if ( retainedTableReferences.contains( joinedTableReference ) ) {
|
if ( retainedTableReferences.contains( joinedTableReference ) ) {
|
||||||
final TableReferenceJoin join = oldJoin.getJoinType() != SqlAstJoinType.INNER
|
final TableReferenceJoin join = oldJoin.getJoinType() != SqlAstJoinType.INNER
|
||||||
&& sharedSuperclassTables.contains( joinedTableReference.getTableExpression() )
|
&& tablesToInnerJoin.contains( joinedTableReference.getTableExpression() )
|
||||||
? new TableReferenceJoin(true, joinedTableReference, oldJoin.getPredicate())
|
? new TableReferenceJoin( true, joinedTableReference, oldJoin.getPredicate() )
|
||||||
: oldJoin;
|
: oldJoin;
|
||||||
tableReferenceJoins.add( join );
|
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 {
|
else {
|
||||||
|
|
|
@ -8,10 +8,10 @@ package org.hibernate.persister.entity;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.Internal;
|
import org.hibernate.Internal;
|
||||||
|
@ -23,6 +23,7 @@ import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
||||||
import org.hibernate.internal.DynamicFilterAliasGenerator;
|
import org.hibernate.internal.DynamicFilterAliasGenerator;
|
||||||
import org.hibernate.internal.FilterAliasGenerator;
|
import org.hibernate.internal.FilterAliasGenerator;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
import org.hibernate.jdbc.Expectation;
|
import org.hibernate.jdbc.Expectation;
|
||||||
import org.hibernate.mapping.Column;
|
import org.hibernate.mapping.Column;
|
||||||
|
@ -34,13 +35,13 @@ import org.hibernate.mapping.Selectable;
|
||||||
import org.hibernate.mapping.Subclass;
|
import org.hibernate.mapping.Subclass;
|
||||||
import org.hibernate.mapping.Table;
|
import org.hibernate.mapping.Table;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
|
||||||
import org.hibernate.metamodel.mapping.TableDetails;
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||||
import org.hibernate.persister.spi.PersisterCreationContext;
|
import org.hibernate.persister.spi.PersisterCreationContext;
|
||||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||||
import org.hibernate.sql.InFragment;
|
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.NamedTableReference;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
||||||
|
@ -660,12 +661,36 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
|
public void pruneForSubclasses(TableGroup tableGroup, Map<String, EntityNameUse> entityNameUses) {
|
||||||
if ( !needsDiscriminator() && treatedEntityNames.isEmpty() ) {
|
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;
|
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();
|
final InFragment frag = new InFragment();
|
||||||
if ( isDiscriminatorFormula() ) {
|
if ( isDiscriminatorFormula() ) {
|
||||||
|
@ -674,34 +699,55 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
||||||
else {
|
else {
|
||||||
frag.setColumn( "t", getDiscriminatorColumnName() );
|
frag.setColumn( "t", getDiscriminatorColumnName() );
|
||||||
}
|
}
|
||||||
|
boolean containsNotNull = false;
|
||||||
final MappingMetamodelImplementor mappingMetamodel = getFactory()
|
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||||
.getRuntimeMetamodels()
|
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
|
||||||
.getMappingMetamodel();
|
if ( useKind == EntityNameUse.UseKind.PROJECTION || useKind == EntityNameUse.UseKind.EXPRESSION ) {
|
||||||
for ( String subclass : treatedEntityNames ) {
|
// We only care about treat and filter uses which allow to reduce the amount of rows to select
|
||||||
final EntityMappingType treatTargetType = mappingMetamodel.getEntityDescriptor( subclass );
|
continue;
|
||||||
if ( !treatTargetType.isAbstract() ) {
|
|
||||||
frag.addValue( treatTargetType.getDiscriminatorSQLValue() );
|
|
||||||
}
|
}
|
||||||
if ( treatTargetType.hasSubclasses() ) {
|
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entry.getKey() );
|
||||||
// if the treat is an abstract class, add the concrete implementations to values if any
|
// Filtering for abstract entities makes no sense, so ignore that
|
||||||
final Set<String> actualSubClasses = treatTargetType.getSubclassEntityNames();
|
// Also, it makes no sense to filter for any of the super types,
|
||||||
for ( String actualSubClass : actualSubClasses ) {
|
// as the query will contain a filter for that already anyway
|
||||||
if ( actualSubClass.equals( subclass ) ) {
|
if ( !persister.isAbstract() && ( this == persister || !isTypeOrSuperType( persister ) ) ) {
|
||||||
continue;
|
containsNotNull = containsNotNull || InFragment.NOT_NULL.equals( persister.getDiscriminatorSQLValue() );
|
||||||
}
|
frag.addValue( persister.getDiscriminatorSQLValue() );
|
||||||
|
|
||||||
final EntityMappingType actualEntityDescriptor = mappingMetamodel.getEntityDescriptor( actualSubClass );
|
|
||||||
if ( !actualEntityDescriptor.hasSubclasses() ) {
|
|
||||||
frag.addValue( actualEntityDescriptor.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(
|
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
|
||||||
"(select * from " + getTableName() + " t where " + frag.toFragmentString() + ")"
|
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
|
@Override
|
||||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.persister.entity;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -53,8 +54,6 @@ import org.hibernate.persister.spi.PersisterCreationContext;
|
||||||
import org.hibernate.spi.NavigablePath;
|
import org.hibernate.spi.NavigablePath;
|
||||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
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.NamedTableReference;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
|
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
|
||||||
|
@ -421,13 +420,13 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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() );
|
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference( getRootTableName() );
|
||||||
if ( tableReference == null ) {
|
if ( tableReference == null ) {
|
||||||
throw new UnknownTableReferenceException( getRootTableName(), "Couldn't find table reference" );
|
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
|
// 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
|
@Override
|
||||||
|
@ -493,9 +492,9 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
}
|
}
|
||||||
|
|
||||||
final StringBuilder subquery = new StringBuilder()
|
final StringBuilder subquery = new StringBuilder()
|
||||||
.append( "( " );
|
.append( "(" );
|
||||||
|
|
||||||
List<PersistentClass> classes = new JoinedList<>(
|
final List<PersistentClass> classes = new JoinedList<>(
|
||||||
List.of( model ),
|
List.of( model ),
|
||||||
Collections.unmodifiableList( model.getSubclasses() )
|
Collections.unmodifiableList( model.getSubclasses() )
|
||||||
);
|
);
|
||||||
|
@ -504,7 +503,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
Table table = clazz.getTable();
|
Table table = clazz.getTable();
|
||||||
if ( !table.isAbstractUnionTable() ) {
|
if ( !table.isAbstractUnionTable() ) {
|
||||||
//TODO: move to .sql package!!
|
//TODO: move to .sql package!!
|
||||||
if ( subquery.length() > 2 ) {
|
if ( subquery.length() > 1 ) {
|
||||||
subquery.append( " union " );
|
subquery.append( " union " );
|
||||||
if ( dialect.supportsUnionAll() ) {
|
if ( dialect.supportsUnionAll() ) {
|
||||||
subquery.append( "all " );
|
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() ) {
|
if ( !hasSubclasses() ) {
|
||||||
return getTableName();
|
return getTableName();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
final Dialect dialect = getFactory().getJdbcServices().getDialect();
|
||||||
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
|
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
|
||||||
|
|
||||||
// Collect all selectables of every entity subtype and group by selection expression as well as table name
|
// 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<>();
|
final LinkedHashMap<String, Map<String, SelectableMapping>> selectables = new LinkedHashMap<>();
|
||||||
// Collect the concrete subclass table names for the treated entity names
|
final Set<String> tablesToUnion = new HashSet<>( entityNameUses.size() );
|
||||||
final Set<String> treatedTableNames = new HashSet<>( treated.size() );
|
// Check if there are filter uses and if so, we know the set of tables to union already
|
||||||
for ( String subclassName : treated ) {
|
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
|
||||||
final UnionSubclassEntityPersister subPersister =
|
final UnionSubclassEntityPersister persister =
|
||||||
(UnionSubclassEntityPersister) metamodel.getEntityDescriptor( subclassName );
|
(UnionSubclassEntityPersister) metamodel.getEntityDescriptor( entry.getKey() );
|
||||||
// Collect all the real (non-abstract) table names
|
if ( entry.getValue().getKind() == EntityNameUse.UseKind.FILTER && !persister.isAbstract() ) {
|
||||||
treatedTableNames.addAll( Arrays.asList( subPersister.getConstraintOrderedTableNameClosure() ) );
|
tablesToUnion.add( persister.getRootTableName() );
|
||||||
|
}
|
||||||
// Collect selectables grouped by the table names in which they appear
|
// Collect selectables grouped by the table names in which they appear
|
||||||
// TODO: we could cache this
|
persister.collectSelectableOwners( selectables );
|
||||||
subPersister.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)
|
// Create a union sub-query for the table names, like generateSubquery(PersistentClass model, Mapping mapping)
|
||||||
final StringBuilder buf = new StringBuilder( subquery.length() )
|
final StringBuilder buf = new StringBuilder( subquery.length() ).append( "(" );
|
||||||
.append( "( " );
|
|
||||||
final StringBuilderSqlAppender sqlAppender = new StringBuilderSqlAppender( buf );
|
|
||||||
|
|
||||||
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 AbstractEntityPersister persister = (AbstractEntityPersister) mappingType;
|
||||||
final String subclassTableName = persister.getTableName();
|
final String subclassTableName;
|
||||||
if ( treatedTableNames.contains( subclassTableName ) ) {
|
if ( persister.hasSubclasses() ) {
|
||||||
if ( buf.length() > 2 ) {
|
subclassTableName = persister.getRootTableName();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subclassTableName = persister.getTableName();
|
||||||
|
}
|
||||||
|
if ( tablesToUnion.contains( subclassTableName ) ) {
|
||||||
|
if ( buf.length() > 1 ) {
|
||||||
buf.append(" union ");
|
buf.append(" union ");
|
||||||
if ( dialect.supportsUnionAll() ) {
|
if ( dialect.supportsUnionAll() ) {
|
||||||
buf.append("all ");
|
buf.append("all ");
|
||||||
|
@ -577,7 +599,12 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
buf.append( dialect.getSelectClauseNullString( sqlType, getFactory().getTypeConfiguration() ) )
|
buf.append( dialect.getSelectClauseNullString( sqlType, getFactory().getTypeConfiguration() ) )
|
||||||
.append( " as " );
|
.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( ", " );
|
||||||
}
|
}
|
||||||
buf.append( persister.getDiscriminatorSQLValue() )
|
buf.append( persister.getDiscriminatorSQLValue() )
|
||||||
|
@ -585,7 +612,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
.append( subclassTableName );
|
.append( subclassTableName );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buf.append( " )" ).toString();
|
return buf.append( ")" ).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void collectSelectableOwners(LinkedHashMap<String, Map<String, SelectableMapping>> selectables) {
|
private void collectSelectableOwners(LinkedHashMap<String, Map<String, SelectableMapping>> selectables) {
|
||||||
|
@ -602,7 +629,14 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
selectable.getSelectionExpression(),
|
selectable.getSelectionExpression(),
|
||||||
k -> new HashMap<>()
|
k -> new HashMap<>()
|
||||||
);
|
);
|
||||||
selectableMapping.put( getTableName(), selectable );
|
final String subclassTableName;
|
||||||
|
if ( hasSubclasses() ) {
|
||||||
|
subclassTableName = getRootTableName();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subclassTableName = getTableName();
|
||||||
|
}
|
||||||
|
selectableMapping.put( subclassTableName, selectable );
|
||||||
};
|
};
|
||||||
getIdentifierMapping().forEachSelectable( selectableConsumer );
|
getIdentifierMapping().forEachSelectable( selectableConsumer );
|
||||||
if ( getVersionMapping() != null ) {
|
if ( getVersionMapping() != null ) {
|
||||||
|
@ -613,6 +647,11 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
for ( int i = 0; i < size; i++ ) {
|
for ( int i = 0; i < size; i++ ) {
|
||||||
attributeMappings.get( i ).forEachSelectable( selectableConsumer );
|
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();
|
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() );
|
navigablePath = navigablePath.append( CollectionPart.Nature.ELEMENT.getName() );
|
||||||
}
|
}
|
||||||
return buildSubNavigablePath( navigablePath, subNavigable, alias );
|
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.BasicValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.MappingType;
|
||||||
import org.hibernate.metamodel.mapping.ModelPart;
|
import org.hibernate.metamodel.mapping.ModelPart;
|
||||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmFrom;
|
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.spi.NavigablePath;
|
||||||
import org.hibernate.query.SemanticException;
|
import org.hibernate.query.SemanticException;
|
||||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||||
|
@ -45,25 +48,37 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
||||||
public static <T> BasicValuedPathInterpretation<T> from(
|
public static <T> BasicValuedPathInterpretation<T> from(
|
||||||
SqmBasicValuedSimplePath<T> sqmPath,
|
SqmBasicValuedSimplePath<T> sqmPath,
|
||||||
SqlAstCreationState sqlAstCreationState,
|
SqlAstCreationState sqlAstCreationState,
|
||||||
SemanticQueryWalker sqmWalker,
|
SemanticQueryWalker<?> sqmWalker,
|
||||||
boolean jpaQueryComplianceEnabled,
|
boolean jpaQueryComplianceEnabled,
|
||||||
Clause currentClause) {
|
Clause currentClause) {
|
||||||
final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess();
|
final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess();
|
||||||
final TableGroup tableGroup = fromClauseAccess.getTableGroup( sqmPath.getNavigablePath().getParent() );
|
final TableGroup tableGroup = fromClauseAccess.getTableGroup( sqmPath.getNavigablePath().getParent() );
|
||||||
|
|
||||||
|
final SqmPath<?> lhs = sqmPath.getLhs();
|
||||||
EntityMappingType treatTarget = null;
|
EntityMappingType treatTarget = null;
|
||||||
if ( jpaQueryComplianceEnabled ) {
|
final ModelPartContainer modelPartContainer;
|
||||||
if ( sqmPath.getLhs() instanceof SqmTreatedPath ) {
|
if ( lhs instanceof SqmTreatedPath<?, ?> ) {
|
||||||
final EntityDomainType treatTargetDomainType = ( (SqmTreatedPath) sqmPath.getLhs() ).getTreatTarget();
|
final EntityDomainType<?> treatTargetDomainType = ( (SqmTreatedPath<?, ?>) lhs ).getTreatTarget();
|
||||||
|
|
||||||
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
||||||
.getSessionFactory()
|
.getSessionFactory()
|
||||||
.getRuntimeMetamodels()
|
.getRuntimeMetamodels()
|
||||||
.getMappingMetamodel();
|
.getMappingMetamodel();
|
||||||
treatTarget = mappingMetamodel.findEntityDescriptor( treatTargetDomainType.getHibernateEntityName() );
|
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 ) {
|
else {
|
||||||
final EntityDomainType entityDomainType = (EntityDomainType) sqmPath.getLhs().getNodeType();
|
modelPartContainer = treatEntityDescriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modelPartContainer = tableGroup.getModelPart();
|
||||||
|
if ( jpaQueryComplianceEnabled && lhs.getNodeType() instanceof EntityDomainType<?> ) {
|
||||||
|
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) lhs.getNodeType();
|
||||||
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
final MappingMetamodel mappingMetamodel = sqlAstCreationState.getCreationContext()
|
||||||
.getSessionFactory()
|
.getSessionFactory()
|
||||||
.getRuntimeMetamodels()
|
.getRuntimeMetamodels()
|
||||||
|
@ -72,20 +87,19 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ModelPartContainer modelPart = tableGroup.getModelPart();
|
|
||||||
final BasicValuedModelPart mapping;
|
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,
|
// 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.
|
// 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 )
|
if ( ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
|
||||||
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
|
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
|
||||||
&& modelPart.getPartMappingType() instanceof ManagedMappingType ) {
|
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType ) {
|
||||||
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPart.getPartMappingType() ).findSubPart(
|
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
|
||||||
sqmPath.getReferencedPathSource().getPathName(),
|
sqmPath.getReferencedPathSource().getPathName(),
|
||||||
treatTarget
|
treatTarget
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mapping = (BasicValuedModelPart) modelPart.findSubPart(
|
mapping = (BasicValuedModelPart) modelPartContainer.findSubPart(
|
||||||
sqmPath.getReferencedPathSource().getPathName(),
|
sqmPath.getReferencedPathSource().getPathName(),
|
||||||
treatTarget
|
treatTarget
|
||||||
);
|
);
|
||||||
|
@ -153,6 +167,11 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
||||||
return columnReference;
|
return columnReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColumnReference getColumnReference() {
|
||||||
|
return columnReference;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||||
columnReference.accept( sqlTreeWalker );
|
columnReference.accept( sqlTreeWalker );
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||||
import org.hibernate.sql.ast.Clause;
|
import org.hibernate.sql.ast.Clause;
|
||||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
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.spi.SqlSelection;
|
||||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
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.QueryPart;
|
||||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||||
import org.hibernate.sql.results.graph.FetchParent;
|
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.descriptor.java.JavaType;
|
||||||
import org.hibernate.type.spi.TypeConfiguration;
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ public class SqlAstQueryPartProcessingStateImpl
|
||||||
implements SqlAstQueryPartProcessingState {
|
implements SqlAstQueryPartProcessingState {
|
||||||
|
|
||||||
private final QueryPart queryPart;
|
private final QueryPart queryPart;
|
||||||
|
private final Map<TableGroup, Map<EntityDomainType<?>, Boolean>> treatRegistrations = new HashMap<>();
|
||||||
private final boolean deduplicateSelectionItems;
|
private final boolean deduplicateSelectionItems;
|
||||||
private FetchParent nestingFetchParent;
|
private FetchParent nestingFetchParent;
|
||||||
|
|
||||||
|
@ -74,6 +76,30 @@ public class SqlAstQueryPartProcessingStateImpl
|
||||||
return queryPart;
|
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
|
// SqlExpressionResolver
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package org.hibernate.query.sqm.tree.domain;
|
package org.hibernate.query.sqm.tree.domain;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -502,6 +503,11 @@ public class SqmPolymorphicRootDescriptor<T> implements EntityDomainType<T> {
|
||||||
throw new UnsupportedOperationException( );
|
throw new UnsupportedOperationException( );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends EntityDomainType<? extends T>> getSubTypes() {
|
||||||
|
throw new UnsupportedOperationException( );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addSubType(ManagedDomainType subType) {
|
public void addSubType(ManagedDomainType subType) {
|
||||||
throw new UnsupportedOperationException( );
|
throw new UnsupportedOperationException( );
|
||||||
|
|
|
@ -57,6 +57,10 @@ public class InFragment {
|
||||||
return setColumn( this.columnName );
|
return setColumn( this.columnName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Object> getValues() {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
public String toFragmentString() {
|
public String toFragmentString() {
|
||||||
if ( values.size() == 0 ) {
|
if ( values.size() == 0 ) {
|
||||||
return "1=2";
|
return "1=2";
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql.ast.spi;
|
package org.hibernate.sql.ast.spi;
|
||||||
|
|
||||||
|
import org.hibernate.Internal;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
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
|
* Access to stuff used while creating a SQL AST
|
||||||
|
@ -28,4 +31,15 @@ public interface SqlAstCreationState {
|
||||||
LoadQueryInfluencers getLoadQueryInfluencers();
|
LoadQueryInfluencers getLoadQueryInfluencers();
|
||||||
|
|
||||||
void registerLockMode(String identificationVariable, LockMode explicitLockMode);
|
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;
|
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;
|
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SqlAstProcessingState specialization for
|
* SqlAstProcessingState specialization for query parts
|
||||||
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState {
|
public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState {
|
||||||
|
@ -18,4 +23,13 @@ public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState {
|
||||||
* considered in-flight as it is probably still being built.
|
* considered in-flight as it is probably still being built.
|
||||||
*/
|
*/
|
||||||
QueryPart getInflightQueryPart();
|
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;
|
this.joinPredicateConsumer = joinPredicateConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TableGroup getCorrelatedTableGroup() {
|
||||||
|
return correlatedTableGroup;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTableGroupJoin(TableGroupJoin join) {
|
public void addTableGroupJoin(TableGroupJoin join) {
|
||||||
assert !getTableGroupJoins().contains( join );
|
assert !getTableGroupJoins().contains( join );
|
||||||
|
@ -155,8 +159,4 @@ public class CorrelatedTableGroup extends AbstractTableGroup {
|
||||||
public Consumer<Predicate> getJoinPredicateConsumer() {
|
public Consumer<Predicate> getJoinPredicateConsumer() {
|
||||||
return joinPredicateConsumer;
|
return joinPredicateConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TableGroup getCorrelatedTableGroup(){
|
|
||||||
return correlatedTableGroup;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql.ast.tree.from;
|
package org.hibernate.sql.ast.tree.from;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,4 +19,15 @@ public interface PluralTableGroup extends TableGroup {
|
||||||
TableGroup getElementTableGroup();
|
TableGroup getElementTableGroup();
|
||||||
|
|
||||||
TableGroup getIndexTableGroup();
|
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.BitSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -446,11 +447,12 @@ public class EntityMetamodel implements Serializable {
|
||||||
hasOwnedCollections = foundOwnedCollection;
|
hasOwnedCollections = foundOwnedCollection;
|
||||||
mutablePropertiesIndexes = mutableIndexes;
|
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() ) {
|
for ( Subclass subclass : persistentClass.getSubclasses() ) {
|
||||||
subclassEntityNamesLocal.add( subclass.getEntityName() );
|
subclassEntityNamesLocal.add( subclass.getEntityName() );
|
||||||
}
|
}
|
||||||
subclassEntityNamesLocal.add( name );
|
|
||||||
subclassEntityNames = toSmallSet( subclassEntityNamesLocal );
|
subclassEntityNames = toSmallSet( subclassEntityNamesLocal );
|
||||||
|
|
||||||
HashMap<Class<?>, String> entityNameByInheritanceClassMapLocal = new HashMap<>();
|
HashMap<Class<?>, String> entityNameByInheritanceClassMapLocal = new HashMap<>();
|
||||||
|
|
|
@ -8,6 +8,9 @@ package org.hibernate.orm.test.inheritance;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.internal.util.ExceptionHelper;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.ConstraintMode;
|
import jakarta.persistence.ConstraintMode;
|
||||||
import jakarta.persistence.DiscriminatorColumn;
|
import jakarta.persistence.DiscriminatorColumn;
|
||||||
|
@ -27,23 +30,24 @@ import jakarta.persistence.criteria.ParameterExpression;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
import jakarta.persistence.criteria.Predicate;
|
||||||
import jakarta.persistence.criteria.Root;
|
import jakarta.persistence.criteria.Root;
|
||||||
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
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.SessionFactory;
|
||||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
@TestForIssue(jiraKey = "HHH-14103")
|
@JiraKey("HHH-14103")
|
||||||
@DomainModel(
|
@DomainModel(
|
||||||
annotatedClasses = {
|
annotatedClasses = {
|
||||||
TransientOverrideAsPersistentJoined.Employee.class,
|
TransientOverrideAsPersistentJoined.Employee.class,
|
||||||
|
@ -114,25 +118,24 @@ public class TransientOverrideAsPersistentJoined {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@FailureExpected(jiraKey = "HHH-12981")
|
@JiraKey("HHH-12981")
|
||||||
public void testQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) {
|
public void testQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( session -> {
|
scope.inTransaction( session -> {
|
||||||
final Employee editor = session.createQuery( "from Employee where title=:title", Employee.class )
|
try {
|
||||||
.setParameter( "title", "Senior Editor" )
|
session.createQuery( "from Employee where title=:title", Employee.class );
|
||||||
.getSingleResult();
|
fail( "Expected exception!" );
|
||||||
assertThat( editor, instanceOf( Editor.class ) );
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
final Employee writer = session.createQuery( "from Employee where title=:title", Employee.class )
|
assertThat(
|
||||||
.setParameter( "title", "Writing" )
|
ExceptionHelper.getRootCause( e ).getMessage(),
|
||||||
.getSingleResult();
|
containsString( "due to the attribute being declared in multiple sub types" )
|
||||||
assertThat( writer, instanceOf( Writer.class ) );
|
);
|
||||||
assertNotNull( ( (Writer) writer ).getGroup() );
|
}
|
||||||
assertEquals( writer.getTitle(), ( (Writer) writer ).getGroup().getName() );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@FailureExpected(jiraKey = "HHH-12981")
|
@JiraKey("HHH-12981")
|
||||||
public void testQueryByRootClassAndOverridenPropertyTreat(SessionFactoryScope scope) {
|
public void testQueryByRootClassAndOverridenPropertyTreat(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( session -> {
|
scope.inTransaction( session -> {
|
||||||
final Employee editor = session.createQuery(
|
final Employee editor = session.createQuery(
|
||||||
|
@ -172,7 +175,7 @@ public class TransientOverrideAsPersistentJoined {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@FailureExpected(jiraKey = "HHH-12981")
|
@JiraKey("HHH-12981")
|
||||||
public void testCriteriaQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) {
|
public void testCriteriaQueryByRootClassAndOverridenProperty(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( session -> {
|
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 );
|
Root<Thing> thingRoot = criteria.from( Thing.class );
|
||||||
|
|
||||||
criteria.select( thingRoot );
|
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 ) ) );
|
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 {
|
finally {
|
||||||
em.close();
|
em.close();
|
||||||
|
|
|
@ -13,7 +13,6 @@ import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.Inheritance;
|
import jakarta.persistence.Inheritance;
|
||||||
import jakarta.persistence.InheritanceType;
|
import jakarta.persistence.InheritanceType;
|
||||||
|
|
||||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
import org.hibernate.testing.orm.junit.JiraKey;
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
@ -37,7 +36,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
public class JoinedSubclassDuplicateFieldsWithTreatTest {
|
public class JoinedSubclassDuplicateFieldsWithTreatTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@FailureExpected( jiraKey = "HHH-11686" )
|
|
||||||
public void queryConstrainedSubclass(SessionFactoryScope scope) {
|
public void queryConstrainedSubclass(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
Deposit deposit1 = new Deposit();
|
Deposit deposit1 = new Deposit();
|
||||||
|
|
Loading…
Reference in New Issue