HHH-12993 Omit joining of superclass table when querying subclass only

This commit is contained in:
Ladislav Kulhanek 2018-10-11 15:49:44 +02:00 committed by Steve Ebersole
parent da847f4b57
commit e0f4047429
25 changed files with 1224 additions and 61 deletions

View File

@ -99,6 +99,7 @@ import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS;
import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH;
import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER;
import static org.hibernate.cfg.AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE;
import static org.hibernate.cfg.AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES;
import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS;
import static org.hibernate.cfg.AvailableSettings.JPA_CALLBACKS_ENABLED;
import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES;
@ -212,6 +213,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private final boolean procedureParameterNullPassingEnabled;
private final boolean collectionJoinSubqueryRewriteEnabled;
private boolean jdbcStyleParamsZeroBased;
private final boolean omitJoinOfSuperclassTablesEnabled;
// Caching
private boolean secondLevelCacheEnabled;
@ -360,6 +362,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
CONVENTIONAL_JAVA_CONSTANTS, BOOLEAN, true );
this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false );
this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true );
this.omitJoinOfSuperclassTablesEnabled = cfgService.getSetting( OMIT_JOIN_OF_SUPERCLASS_TABLES, BOOLEAN, true );
final RegionFactory regionFactory = serviceRegistry.getService( RegionFactory.class );
if ( !NoCachingRegionFactory.class.isInstance( regionFactory ) ) {
@ -1064,6 +1067,12 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return enhancementAsProxyEnabled;
}
@Override
public boolean isOmitJoinOfSuperclassTablesEnabled() {
return omitJoinOfSuperclassTablesEnabled;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In-flight mutation access

View File

@ -447,4 +447,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
public boolean isEnhancementAsProxyEnabled() {
return delegate.isEnhancementAsProxyEnabled();
}
@Override
public boolean isOmitJoinOfSuperclassTablesEnabled() {
return delegate.isOmitJoinOfSuperclassTablesEnabled();
}
}

View File

@ -314,4 +314,6 @@ public interface SessionFactoryOptions {
default boolean isEnhancementAsProxyEnabled() {
return false;
}
boolean isOmitJoinOfSuperclassTablesEnabled();
}

View File

@ -2060,4 +2060,19 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
* @since 5.4
*/
String SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY = "hibernate.id.sequence.increment_size_mismatch_strategy";
/**
* <p>
* When you use {@link javax.persistence.InheritanceType#JOINED} strategy for inheritance mapping and query
* a value from an entity, all superclass tables are joined in the query regardless you need them. With
* this setting set to true only superclass tables which are really needed are joined.
* </p>
* <p>
* The default value is true.
* </p>
*
* @since 5.4
*/
String OMIT_JOIN_OF_SUPERCLASS_TABLES = "hibernate.query.omit_join_of_superclass_tables";
}

View File

@ -47,6 +47,7 @@ public class JoinSequence {
private Selector selector;
private JoinSequence next;
private boolean isFromPart;
private Set<String> queryReferencedTables;
/**
* Constructs a JoinSequence
@ -466,7 +467,7 @@ public class JoinSequence {
Set<String> treatAsDeclarations) {
final boolean include = includeSubclassJoins && isIncluded( alias );
joinFragment.addJoins(
joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations ),
joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations, queryReferencedTables ),
joinable.whereJoinFragment( alias, innerJoin, include, treatAsDeclarations )
);
}
@ -573,6 +574,15 @@ public class JoinSequence {
return useThetaStyle;
}
/**
* Set all tables the query refers to. It allows to optimize the query.
*
* @param queryReferencedTables
*/
public void setQueryReferencedTables(Set<String> queryReferencedTables) {
this.queryReferencedTables = queryReferencedTables;
}
public Join getFirstJoin() {
return joins.get( 0 );
}

View File

@ -11,6 +11,8 @@ import java.util.Map;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type;
@ -123,4 +125,19 @@ public abstract class AbstractMapComponentNode extends FromReferenceNode impleme
return MapKeyEntityFromElement.buildKeyJoin( getFromElement() );
}
@Override
public String[] getReferencedTables() {
String[] referencedTables = null;
FromElement fromElement = getFromElement();
if ( fromElement != null ) {
EntityPersister entityPersister = fromElement.getEntityPersister();
if ( entityPersister != null && entityPersister instanceof AbstractEntityPersister ) {
AbstractEntityPersister abstractEntityPersister = (AbstractEntityPersister) entityPersister;
referencedTables = abstractEntityPersister.getTableNames();
}
}
return referencedTables;
}
}

View File

@ -76,10 +76,18 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
* The identifier that is the name of the property.
*/
private String propertyName;
/**
* The identifier that is the name of the property. In comparison with {@link #propertyName}
* it is always identical with identifier in the query, it is not changed during processing.
*/
private String originalPropertyName;
/**
* The full path, to the root alias of this dot node.
*/
private String path;
/**
* The unresolved property path relative to this dot node.
*/
@ -160,6 +168,7 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
// Set the attributes of the property reference expression.
String propName = property.getText();
propertyName = propName;
originalPropertyName = propName;
// If the uresolved property path isn't set yet, just use the property name.
if ( propertyPath == null ) {
propertyPath = propName;
@ -692,6 +701,25 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
return super.getDataType();
}
@Override
public String[] getReferencedTables() {
String[] referencedTables = null;
AST firstChild = getFirstChild();
if ( firstChild != null ) {
if ( firstChild instanceof FromReferenceNode ) {
FromReferenceNode fromReferenceNode = (FromReferenceNode) firstChild;
FromElement fromElement = fromReferenceNode.getFromElement();
if ( fromElement != null ) {
String table = fromElement.getPropertyTableName( getOriginalPropertyName() );
if ( table != null ) {
referencedTables = new String[] { table };
}
}
}
}
return referencedTables;
}
public void setPropertyPath(String propertyPath) {
this.propertyPath = propertyPath;
}
@ -700,6 +728,14 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec
return propertyPath;
}
public String getPropertyName() {
return propertyName;
}
public String getOriginalPropertyName() {
return originalPropertyName;
}
public FromReferenceNode getLhs() {
FromReferenceNode lhs = ( (FromReferenceNode) getFirstChild() );
if ( lhs == null ) {

View File

@ -517,6 +517,10 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
return elementType.getPropertyType( propertyName, propertyPath );
}
public String getPropertyTableName(String propertyName) {
return elementType.getPropertyTableName( propertyName );
}
public String[] toColumns(String tableAlias, String path, boolean inSelect) {
return elementType.toColumns( tableAlias, path, inSelect );
}

View File

@ -27,6 +27,7 @@ import org.hibernate.param.ParameterSpecification;
import org.hibernate.persister.collection.CollectionPropertyMapping;
import org.hibernate.persister.collection.CollectionPropertyNames;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.PropertyMapping;
@ -372,6 +373,15 @@ class FromElementType {
return queryableCollection;
}
public String getPropertyTableName(String propertyName) {
checkInitialized();
if ( this.persister != null ) {
AbstractEntityPersister aep = (AbstractEntityPersister) this.persister;
return aep.getPropertyTableName( propertyName );
}
return null;
}
/**
* Returns the type of a property, given it's name (the last part) and the full path.
*

View File

@ -137,4 +137,14 @@ public abstract class FromReferenceNode extends AbstractSelectExpression
|| getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE;
}
/**
* Returns table names which are referenced by this node. If the tables
* can not be determined it returns null.
*
* @return table names or null.
*/
public String[] getReferencedTables() {
return null;
}
}

View File

@ -10,6 +10,8 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.hql.internal.ast.tree.DisplayableNode;
@ -25,7 +27,7 @@ import antlr.collections.AST;
* @author Joshua Davis
* @author Steve Ebersole
*/
public final class ASTPrinter {
public class ASTPrinter {
// This is a map: array index is the ANTLR Token ID, array value is the name of that token.
// There might be gaps in the array (null values) but it's generally quite compact.
@ -103,15 +105,7 @@ public final class ASTPrinter {
return;
}
for ( AST parent : parents ) {
if ( parent.getNextSibling() == null ) {
pw.print( " " );
}
else {
pw.print( " | " );
}
}
indentLine( parents, pw );
if ( ast.getNextSibling() == null ) {
pw.print( " \\-" );
@ -121,6 +115,7 @@ public final class ASTPrinter {
}
showNode( pw, ast );
showNodeProperties( parents, pw, ast );
ArrayList<AST> newParents = new ArrayList<AST>( parents );
newParents.add( ast );
@ -130,6 +125,17 @@ public final class ASTPrinter {
newParents.clear();
}
private void indentLine(List<AST> parents, PrintWriter pw) {
for ( AST parent : parents ) {
if ( parent.getNextSibling() == null ) {
pw.print( " " );
}
else {
pw.print( " | " );
}
}
}
private void showNode(PrintWriter pw, AST ast) {
String s = nodeToString( ast );
pw.println( s );
@ -158,6 +164,24 @@ public final class ASTPrinter {
return buf.toString();
}
private void showNodeProperties(ArrayList<AST> parents, PrintWriter pw, AST ast) {
Map<String, Object> nodeProperties = createNodeProperties( ast );
ArrayList<AST> parentsAndNode = new ArrayList<>( parents );
parentsAndNode.add( ast );
for ( String propertyName : nodeProperties.keySet() ) {
indentLine( parentsAndNode, pw );
pw.println( propertyToString( propertyName, nodeProperties.get( propertyName ), ast ) );
}
}
public LinkedHashMap<String, Object> createNodeProperties(AST ast) {
return new LinkedHashMap<>();
}
public String propertyToString(String label, Object value, AST ast) {
return String.format( "%s: %s", label, value );
}
public static void appendEscapedMultibyteChars(String text, StringBuilder buf) {
char[] chars = text.toCharArray();
for ( char aChar : chars ) {

View File

@ -0,0 +1,72 @@
/*
* 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.hql.internal.ast.util;
import java.util.Arrays;
import java.util.LinkedHashMap;
import org.hibernate.hql.internal.ast.tree.DotNode;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.FromReferenceNode;
import org.hibernate.hql.internal.ast.tree.IdentNode;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import antlr.collections.AST;
public class ASTReferencedTablesPrinter extends ASTPrinter {
public ASTReferencedTablesPrinter(Class tokenTypeConstants) {
super( tokenTypeConstants );
}
@Override
public String nodeToString(AST ast) {
if ( ast == null ) {
return "{node:null}";
}
return ast.getClass().getSimpleName();
}
@Override
public LinkedHashMap<String, Object> createNodeProperties(AST node) {
LinkedHashMap<String, Object> props = new LinkedHashMap<>();
if ( node instanceof FromReferenceNode ) {
FromReferenceNode frn = (FromReferenceNode) node;
FromElement fromElement = frn.getFromElement();
EntityPersister entityPersister = fromElement != null ? fromElement.getEntityPersister() : null;
String entityPersisterStr = entityPersister != null ? entityPersister.toString() : null;
props.put( "persister", entityPersisterStr );
String referencedTablesStr = Arrays.toString( frn.getReferencedTables() );
props.put( "referencedTables", referencedTablesStr );
}
if ( node instanceof DotNode ) {
DotNode dn = (DotNode) node;
props.put( "path", dn.getPath() );
props.put( "originalPropertyName", dn.getOriginalPropertyName() );
}
if ( node instanceof IdentNode ) {
IdentNode in = (IdentNode) node;
props.put( "originalText", in.getOriginalText() );
}
if ( node instanceof SelectClause ) {
SelectClause sc = (SelectClause) node;
for ( Object element : sc.getFromElementsForLoad() ) {
FromElement fromElement = (FromElement) element;
EntityPersister entityPersister = fromElement.getEntityPersister();
if ( entityPersister != null && entityPersister instanceof AbstractEntityPersister ) {
AbstractEntityPersister aep = (AbstractEntityPersister) entityPersister;
String entityClass = aep.getMappedClass().getSimpleName();
String tables = Arrays.toString( aep.getTableNames() );
props.put( String.format( "referencedTables(entity %s)", entityClass ), tables );
}
}
}
return props;
}
}

View File

@ -8,14 +8,17 @@ package org.hibernate.hql.internal.ast.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.AssertionFailure;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.engine.spi.LoadQueryInfluencers;
@ -24,6 +27,7 @@ import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.tree.DotNode;
import org.hibernate.hql.internal.ast.tree.FromClause;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.FromReferenceNode;
import org.hibernate.hql.internal.ast.tree.ImpliedFromElement;
import org.hibernate.hql.internal.ast.tree.ParameterContainer;
import org.hibernate.hql.internal.ast.tree.QueryNode;
@ -33,11 +37,16 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.FilterImpl;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.param.DynamicFilterParameterSpecification;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.JoinType;
import org.hibernate.type.Type;
import antlr.collections.AST;
/**
* Performs the post-processing of the join information gathered during semantic analysis.
* The join generating classes are complex, this encapsulates some of the JoinSequence-related
@ -94,9 +103,86 @@ public class JoinProcessor implements SqlTokenTypes {
}
}
private <T extends AST> List<T> findAllNodes(AST node, Class<T> clazz) {
ArrayList<T> found = new ArrayList<>();
doFindAllNodes( node, clazz, found );
return found;
}
private <T extends AST> void doFindAllNodes(AST node, Class<T> clazz, List<T> found) {
if ( clazz.isAssignableFrom( node.getClass() ) ) {
found.add( (T) node );
}
if ( node.getFirstChild() != null ) {
doFindAllNodes( node.getFirstChild(), clazz, found );
}
if ( node.getNextSibling() != null ) {
doFindAllNodes( node.getNextSibling(), clazz, found );
}
}
private Set<String> findQueryReferencedTables(QueryNode query) {
if ( !walker.getSessionFactoryHelper()
.getFactory()
.getSessionFactoryOptions()
.isOmitJoinOfSuperclassTablesEnabled() ) {
if ( LOG.isDebugEnabled() ) {
LOG.debug( String.format(
"Finding of query referenced tables is skipped because the feature is disabled. See %s",
AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES
) );
}
return null;
}
if ( CollectionHelper.isNotEmpty( walker.getEnabledFilters() ) ) {
LOG.debug( "Finding of query referenced tables is skipped because filters are enabled." );
return null;
}
if ( LOG.isDebugEnabled() ) {
LOG.debug( TokenPrinters.REFERENCED_TABLES_PRINTER.showAsString(
query,
"Tables referenced from query nodes:"
) );
}
Set<String> result = new HashSet<>();
// Find tables referenced by FromReferenceNodes
List<FromReferenceNode> fromReferenceNodes = findAllNodes( query, FromReferenceNode.class );
for ( FromReferenceNode node : fromReferenceNodes ) {
String[] tables = node.getReferencedTables();
if ( tables != null ) {
for ( String table : tables ) {
result.add( table );
}
}
}
// Find tables referenced by fromElementsForLoad
if ( query.getSelectClause() != null ) {
for ( Object element : query.getSelectClause().getFromElementsForLoad() ) {
FromElement fromElement = (FromElement) element;
EntityPersister entityPersister = fromElement.getEntityPersister();
if ( entityPersister != null && entityPersister instanceof AbstractEntityPersister ) {
AbstractEntityPersister aep = (AbstractEntityPersister) entityPersister;
String[] tables = aep.getTableNames();
for ( String table : tables ) {
result.add( table );
}
}
}
}
return result;
}
public void processJoins(QueryNode query) {
final FromClause fromClause = query.getFromClause();
Set<String> queryReferencedTables = findQueryReferencedTables( query );
final List fromElements;
if ( DotNode.useThetaStyleImplicitJoins ) {
// for regression testing against output from the old parser...
@ -136,6 +222,7 @@ public class JoinProcessor implements SqlTokenTypes {
while ( iter.hasNext() ) {
final FromElement fromElement = (FromElement) iter.next();
JoinSequence join = fromElement.getJoinSequence();
join.setQueryReferencedTables( queryReferencedTables );
join.setSelector(
new JoinSequence.Selector() {
public boolean includeSubclasses(String alias) {

View File

@ -21,4 +21,6 @@ public interface TokenPrinters {
ASTPrinter ORDERBY_FRAGMENT_PRINTER = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class );
ASTPrinter REFERENCED_TABLES_PRINTER = new ASTReferencedTablesPrinter( SqlTokenTypes.class );
}

View File

@ -533,6 +533,14 @@ public abstract class AbstractEntityPersister
return propertySelectable;
}
public String[] getTableNames() {
String[] tableNames = new String[getTableSpan()];
for ( int i = 0; i < tableNames.length; i++ ) {
tableNames[i] = getTableName( i );
}
return tableNames;
}
@SuppressWarnings("UnnecessaryBoxing")
public AbstractEntityPersister(
final PersistentClass persistentClass,
@ -3890,7 +3898,8 @@ public abstract class AbstractEntityPersister
alias,
innerJoin,
includeSubclasses,
Collections.emptySet()
Collections.emptySet(),
null
).toFromFragmentString();
}
@ -3903,7 +3912,19 @@ public abstract class AbstractEntityPersister
// NOTE : Not calling createJoin here is just a performance optimization
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations ).toFromFragmentString();
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations, null ).toFromFragmentString();
}
@Override
public String fromJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations,
Set<String> referencedTables) {
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations, referencedTables ).toFromFragmentString();
}
@Override
@ -3915,7 +3936,8 @@ public abstract class AbstractEntityPersister
alias,
innerJoin,
includeSubclasses,
Collections.emptySet()
Collections.emptySet(),
null
).toWhereFragmentString();
}
@ -3928,7 +3950,7 @@ public abstract class AbstractEntityPersister
// NOTE : Not calling createJoin here is just a performance optimization
return getSubclassTableSpan() == 1
? ""
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations ).toWhereFragmentString();
: createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations, null ).toWhereFragmentString();
}
protected boolean isSubclassTableLazy(int j) {
@ -3940,6 +3962,15 @@ public abstract class AbstractEntityPersister
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
return createJoin(name, innerJoin, includeSubclasses, treatAsDeclarations, null);
}
protected JoinFragment createJoin(
String name,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations,
Set<String> referencedTables) {
// IMPL NOTE : all joins join to the pk of the driving table
final String[] idCols = StringHelper.qualify( name, getIdentifierColumnNames() );
final JoinFragment join = getFactory().getDialect().createOuterJoinFragment();
@ -3950,7 +3981,8 @@ public abstract class AbstractEntityPersister
j,
innerJoin,
includeSubclasses,
treatAsDeclarations
treatAsDeclarations,
referencedTables
);
if ( joinType != null && joinType != JoinType.NONE ) {
@ -3971,8 +4003,28 @@ public abstract class AbstractEntityPersister
boolean canInnerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
return determineSubclassTableJoinType(
subclassTableNumber,
canInnerJoin,
includeSubclasses,
treatAsDeclarations,
null
);
}
protected JoinType determineSubclassTableJoinType(
int subclassTableNumber,
boolean canInnerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations,
Set<String> referencedTables) {
if ( isClassOrSuperclassTable( subclassTableNumber ) ) {
String superclassTableName = getSubclassTableName( subclassTableNumber );
if ( referencedTables != null && canOmitSuperclassTableJoin() && !referencedTables.contains(
superclassTableName ) ) {
return JoinType.NONE;
}
final boolean shouldInnerJoin = canInnerJoin
&& !isInverseTable( subclassTableNumber )
&& !isNullableTable( subclassTableNumber );
@ -5735,6 +5787,15 @@ public abstract class AbstractEntityPersister
return ArrayHelper.to2DStringArray( polymorphicJoinColumns );
}
/**
* If true, persister can omit superclass tables during joining if they are not needed in the query.
*
* @return true if the persister can do it
*/
public boolean canOmitSuperclassTableJoin() {
return false;
}
private void prepareEntityIdentifierDefinition() {
if ( entityIdentifierDefinition != null ) {
return;

View File

@ -57,6 +57,19 @@ public interface Joinable {
*/
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses, Set<String> treatAsDeclarations);
/**
* Get the from clause part of any joins
* (optional operation)
*/
default String fromJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations,
Set<String> referencedTables) {
return fromJoinFragment( alias, innerJoin, includeSubclasses, treatAsDeclarations );
}
/**
* The columns to join on
*/

View File

@ -1053,6 +1053,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
return subclassNamesBySubclassTable[index];
}
@Override
public String getPropertyTableName(String propertyName) {
Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName );
if ( index == null ) {
@ -1118,4 +1119,9 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) {
return new DynamicFilterAliasGenerator(subclassTableNameClosure, rootAlias);
}
@Override
public boolean canOmitSuperclassTableJoin() {
return true;
}
}

View File

@ -809,6 +809,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
return isNullableSubclassTable[j];
}
@Override
public String getPropertyTableName(String propertyName) {
Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName );
if ( index == null ) {

View File

@ -466,6 +466,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
return true;
}
@Override
public String getPropertyTableName(String propertyName) {
//TODO: check this....
return getTableName();
@ -483,4 +484,5 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) {
return new StaticFilterAliasGenerator( rootAlias );
}
}

View File

@ -16,6 +16,7 @@ import java.util.Map;
import org.hibernate.QueryException;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.H2Dialect;
@ -154,7 +155,7 @@ public class HQLTest extends QueryTranslatorTestCase {
assertTranslation( "from Animal a where a.offspring.description = 'xyz'" );
assertTranslation( "from Animal a where a.offspring.father.description = 'xyz'" );
}
@Test
@FailureExpected( jiraKey = "N/A", message = "Lacking ClassicQueryTranslatorFactory support" )
public void testRowValueConstructorSyntaxInInList2() {
@ -209,7 +210,7 @@ public class HQLTest extends QueryTranslatorTestCase {
AST inNode = whereNode.getFirstChild();
assertEquals( message, expected, inNode != null && inNode.getType() == HqlTokenTypes.IN );
}
@Test
public void testSubComponentReferences() {
assertTranslation( "select c.address.zip.code from ComponentContainer c" );
@ -227,7 +228,7 @@ public class HQLTest extends QueryTranslatorTestCase {
public void testJoinFetchCollectionOfValues() {
assertTranslation( "select h from Human as h join fetch h.nickNames" );
}
@Test
public void testCollectionMemberDeclarations2() {
assertTranslation( "from Customer c, in(c.orders) o" );
@ -242,11 +243,13 @@ public class HQLTest extends QueryTranslatorTestCase {
// IN asks an alias, but the difference is that the error message from AST
// contains the error token location (by lines and columns), which is hardly
// to get from Classic query translator --stliu
assertTranslation( "from Customer c, in(c.orders)" );
assertTranslation( "from Customer c, in(c.orders)" );
}
@Test
public void testCollectionJoinsInSubselect() {
disableOmittingJoinOfSuperclassTables();
// caused by some goofiness in FromElementFactory that tries to
// handle correlated subqueries (but fails miserably) even though this
// is not a correlated subquery. HHH-1248
@ -340,10 +343,14 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testImplicitJoinsAlongWithCartesianProduct() {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation( "select foo.foo from Foo foo, Foo foo2" );
assertTranslation( "select foo.foo.foo from Foo foo, Foo foo2" );
DotNode.useThetaStyleImplicitJoins = false;
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation("select foo.foo from Foo foo, Foo foo2");
assertTranslation("select foo.foo.foo from Foo foo, Foo foo2");
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
@ -545,18 +552,22 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testCrazyIdFieldNames() {
DotNode.useThetaStyleImplicitJoins = true;
// only regress against non-scalar forms as there appears to be a bug in the classic translator
// in regards to this issue also. Specifically, it interprets the wrong return type, though it gets
// the sql "correct" :/
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
// only regress against non-scalar forms as there appears to be a bug in the classic translator
// in regards to this issue also. Specifically, it interprets the wrong return type, though it gets
// the sql "correct" :/
String hql = "select e.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null";
assertTranslation( hql, new HashMap(), false, null );
String hql = "select e.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null";
assertTranslation(hql, new HashMap(), false, null);
hql = "select e.heresAnotherCrazyIdFieldName.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null";
assertTranslation( hql, new HashMap(), false, null );
hql = "select e.heresAnotherCrazyIdFieldName.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null";
assertTranslation(hql, new HashMap(), false, null);
DotNode.useThetaStyleImplicitJoins = false;
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
@ -784,7 +795,7 @@ public class HQLTest extends QueryTranslatorTestCase {
|| getDialect() instanceof Sybase11Dialect
|| getDialect() instanceof SybaseASE15Dialect
|| getDialect() instanceof SybaseAnywhereDialect
|| getDialect() instanceof SQLServerDialect
|| getDialect() instanceof SQLServerDialect
|| getDialect() instanceof IngresDialect) {
// SybaseASE15Dialect and SybaseAnywhereDialect support '||'
// MySQL uses concat(x, y, z)
@ -878,6 +889,8 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testGroupByFunction() {
disableOmittingJoinOfSuperclassTables();
if ( getDialect() instanceof Oracle8iDialect ) return; // the new hiearchy...
if ( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof PostgreSQL81Dialect ) return;
if ( getDialect() instanceof TeradataDialect) return;
@ -1027,26 +1040,36 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testImplicitJoinInSelect() {
assertTranslation( "select foo, foo.long from Foo foo" );
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation( "select foo.foo from Foo foo" );
assertTranslation( "select foo, foo.foo from Foo foo" );
assertTranslation( "select foo.foo from Foo foo where foo.foo is not null" );
DotNode.useThetaStyleImplicitJoins = false;
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation("select foo.foo from Foo foo");
assertTranslation("select foo, foo.foo from Foo foo");
assertTranslation("select foo.foo from Foo foo where foo.foo is not null");
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
public void testSelectExpressions() {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation( "select an.mother.mother from Animal an" );
assertTranslation( "select an.mother.mother.mother from Animal an" );
assertTranslation( "select an.mother.mother.bodyWeight from Animal an" );
assertTranslation( "select an.mother.zoo.id from Animal an" );
assertTranslation( "select user.human.zoo.id from User user" );
assertTranslation( "select u.userName, u.human.name.first from User u" );
assertTranslation( "select u.human.name.last, u.human.name.first from User u" );
assertTranslation( "select bar.baz.name from Bar bar" );
assertTranslation( "select bar.baz.name, bar.baz.count from Bar bar" );
DotNode.useThetaStyleImplicitJoins = false;
disableOmittingJoinOfSuperclassTables();
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation("select an.mother.mother from Animal an");
assertTranslation("select an.mother.mother.mother from Animal an");
assertTranslation("select an.mother.mother.bodyWeight from Animal an");
assertTranslation("select an.mother.zoo.id from Animal an");
assertTranslation("select user.human.zoo.id from User user");
assertTranslation("select u.userName, u.human.name.first from User u");
assertTranslation("select u.human.name.last, u.human.name.first from User u");
assertTranslation("select bar.baz.name from Bar bar");
assertTranslation("select bar.baz.name, bar.baz.count from Bar bar");
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
@ -1102,10 +1125,14 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testSelectEntityProperty() throws Exception {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation( "select an.mother from Animal an" );
assertTranslation( "select an, an.mother from Animal an" );
DotNode.useThetaStyleImplicitJoins = false;
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation("select an.mother from Animal an");
assertTranslation("select an, an.mother from Animal an");
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
@ -1202,9 +1229,13 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testManyToManyJoinInSubselect() throws Exception {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation( "select foo from Foo foo where foo in (select elt from Baz baz join baz.fooArray elt)" );
DotNode.useThetaStyleImplicitJoins = false;
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation("select foo from Foo foo where foo in (select elt from Baz baz join baz.fooArray elt)");
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
@ -1258,6 +1289,8 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testSelectDialectFunction() throws Exception {
disableOmittingJoinOfSuperclassTables();
// From SQLFunctionsTest.testDialectSQLFunctions...
if ( getDialect() instanceof HSQLDialect ) {
assertTranslation( "select mod(s.count, 2) from org.hibernate.test.legacy.Simple as s where s.id = 10" );
@ -1358,12 +1391,16 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testOneToOne() throws Exception {
disableOmittingJoinOfSuperclassTables();
assertTranslation( "from User u where u.human.nickName='Steve'" );
assertTranslation( "from User u where u.human.name.first='Steve'" );
}
@Test
public void testSelectClauseImplicitJoin() throws Exception {
disableOmittingJoinOfSuperclassTables();
//assertTranslation( "select d.owner.mother from Dog d" ); //bug in old qt
assertTranslation( "select d.owner.mother.description from Dog d" );
//assertTranslation( "select d.owner.mother from Dog d, Dog h" );
@ -1473,10 +1510,14 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testJoinInSubselect() throws Exception {
//new parser uses ANSI-style inner join syntax
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation( "from Animal a where a in (select m from Animal an join an.mother m)" );
assertTranslation( "from Animal a where a in (select o from Animal an join an.offspring o)" );
DotNode.useThetaStyleImplicitJoins = false;
boolean originalValue = DotNode.useThetaStyleImplicitJoins;
try {
DotNode.useThetaStyleImplicitJoins = true;
assertTranslation("from Animal a where a in (select m from Animal an join an.mother m)");
assertTranslation("from Animal a where a in (select o from Animal an join an.offspring o)");
} finally {
DotNode.useThetaStyleImplicitJoins = originalValue;
}
}
@Test
@ -1519,12 +1560,16 @@ public class HQLTest extends QueryTranslatorTestCase {
@Test
public void testManyToManyInJoin() throws Exception {
disableOmittingJoinOfSuperclassTables();
assertTranslation( "select x.id from Human h1 join h1.family x" );
//assertTranslation("select index(h2) from Human h1 join h1.family h2");
}
@Test
public void testManyToManyInSubselect() throws Exception {
disableOmittingJoinOfSuperclassTables();
assertTranslation( "from Human h1, Human h2 where h2 in (select x.id from h1.family x)" );
assertTranslation( "from Human h1, Human h2 where 'father' in indices(h1.family)" );
}
@ -1580,4 +1625,12 @@ public class HQLTest extends QueryTranslatorTestCase {
compileWithAstQueryTranslator( "from Human where name.first = 'Gavin'", false );
}
private void disableOmittingJoinOfSuperclassTables() {
// Disable this feature because of Lacking ClassicQueryTranslatorFactory support
rebuildSessionFactory( c -> c.setProperty(
AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES,
Boolean.FALSE.toString()
) );
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.test.joinwithoutancestor;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
@TestForIssue( jiraKey = "HHH-12993")
public class OmitAncestorJoinTest extends OmitAncestorTestCase {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { A.class, SubA.class, SubSubA.class, B.class };
}
@Test
public void test() {
// Should not join any parent table
assertFromTables("select valSubA from SubA", SubA.TABLE);
// Should not join any parent table
assertFromTables("select sa.valSubA from SubA sa", SubA.TABLE);
// Should omit middle table from inheritance hierarchy
assertFromTables("select ssa.valA from SubSubA ssa", SubSubA.TABLE, A.TABLE);
// Should omit middle table from inheritance hierarchy
assertFromTables( "select ssa.valA, ssa.valSubSubA from SubSubA ssa", SubSubA.TABLE, A.TABLE );
// Should join parent table, because it is used in "where" part
assertFromTables("select valSubA from SubA where valA = 'foo'", SubA.TABLE, A.TABLE);
// Should join parent table, because it is used in "order by" part
assertFromTables("select valSubSubA from SubSubA order by valA", SubSubA.TABLE, A.TABLE);
// Should other tables from hierarchye, because it returns whole entity
assertFromTables("select suba from SubA suba", SubA.TABLE, A.TABLE, SubSubA.TABLE);
assertFromTables("from SubA", SubA.TABLE, A.TABLE, SubSubA.TABLE);
// Should join A table, because it has the reference to B table
assertFromTables( "select suba.b from SubA suba", SubA.TABLE, A.TABLE, B.TABLE );
assertFromTables( "select suba.b.id from SubA suba", SubA.TABLE, A.TABLE );
}
@Entity(name = "A")
@Table(name = A.TABLE)
@Inheritance(strategy = InheritanceType.JOINED)
static class A {
public static final String TABLE = "A_Table";
@Id
@GeneratedValue
private Long id;
private String valA;
@ManyToOne
private B b;
public Long getId() {
return id;
}
public String getValA() {
return valA;
}
public void setValA(String valA) {
this.valA = valA;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
@Entity(name = "SubA")
@Table(name = SubA.TABLE)
static class SubA extends A {
public static final String TABLE = "SubA_Table";
private String valSubA;
public String getValSubA() {
return valSubA;
}
public void setValSubA(String valSubA) {
this.valSubA = valSubA;
}
}
@Entity(name = "SubSubA")
@Table(name = SubSubA.TABLE)
static class SubSubA extends SubA {
public static final String TABLE = "SubSubA_Table";
private String valSubSubA;
public String getValSubSubA() {
return valSubSubA;
}
public void setValSubSubA(String valSubSubA) {
this.valSubSubA = valSubSubA;
}
}
@Entity(name = "B")
@Table(name = B.TABLE)
static class B {
public static final String TABLE = "B_table";
@Id
@GeneratedValue
private Long id;
private String valB;
@OneToMany(mappedBy = "b")
private List<A> aList;
public Long getId() {
return id;
}
public String getValB() {
return valB;
}
public void setValB(String valB) {
this.valB = valB;
}
public List<A> getaList() {
return aList;
}
public void setaList(List<A> aList) {
this.aList = aList;
}
}
}

View File

@ -0,0 +1,262 @@
/*
* 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.test.joinwithoutancestor;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Assert;
import org.junit.Test;
@TestForIssue(jiraKey = "HHH-12993")
public class OmitAncestorJoinWhenCommonSecondaryTablePresentTest extends OmitAncestorTestCase {
private static final String SECONDARY_TABLE_NAME = "secondary_table";
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { A.class, SubA.class, B.class, SubB.class, C.class };
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Override
protected void cleanupTestData() throws Exception {
TransactionUtil.doInHibernate( this::sessionFactory, s -> {
s.createQuery( "from A", A.class ).list().forEach( s::remove );
s.createQuery( "from B", B.class ).list().forEach( s::remove );
s.createQuery( "from C", C.class ).list().forEach( s::remove );
} );
super.cleanupTestData();
}
@Test
public void shouldNotReturnSecondaryTableValueForSubB() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
SubA subA = new SubA( 1L );
subA.setValSubA( "valSubA" );
subA.setValSecondaryTable( "valSecondaryTableFromSubA" );
session.persist( subA );
SubB subB = new SubB( 2L );
subB.setValSubB( "valSubB" );
subB.setValSecondaryTable( "valSecondaryTableFromSubB" );
session.persist( subB );
Query<String> query = session.createQuery( "select suba.valSecondaryTable from SubA suba", String.class );
List<String> resultList = query.getResultList();
Assert.assertEquals( 1, resultList.size() );
Assert.assertEquals( "valSecondaryTableFromSubA", resultList.get( 0 ) );
} );
}
@Test
public void shouldNotReturnSecondaryTableValueForSubB_implicitJoin() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
SubA subA = new SubA( 1L );
subA.setValSubA( "valSubA" );
subA.setValSecondaryTable( "valSecondaryTableFromSubA" );
session.persist( subA );
SubB subB = new SubB( 2L );
subB.setValSubB( "valSubB" );
subB.setValSecondaryTable( "valSecondaryTableFromSubB" );
session.persist( subB );
C c = new C();
c.setSubA( subA );
session.persist( c );
Query<String> query = session.createQuery( "select c.subA.valSecondaryTable from C c", String.class );
List<String> resultList = query.getResultList();
Assert.assertEquals( 1, resultList.size() );
Assert.assertEquals( "valSecondaryTableFromSubA", resultList.get( 0 ) );
} );
}
@Entity(name = "A")
@Table(name = A.TABLE)
@Inheritance(strategy = InheritanceType.JOINED)
static class A {
public static final String TABLE = "A_Table";
public A() {
}
public A(Long id) {
this.id = id;
}
@Id
private Long id;
private String valA;
public Long getId() {
return id;
}
public String getValA() {
return valA;
}
public void setValA(String valA) {
this.valA = valA;
}
}
@Entity(name = "SubA")
@Table(name = SubA.TABLE)
@SecondaryTable(name = SECONDARY_TABLE_NAME, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
static class SubA extends A {
public static final String TABLE = "SubA_table";
public SubA() {
}
public SubA(Long id) {
super( id );
}
private String valSubA;
@Column(table = SECONDARY_TABLE_NAME)
private String valSecondaryTable;
public String getValSubA() {
return valSubA;
}
public void setValSubA(String valSubA) {
this.valSubA = valSubA;
}
public String getValSecondaryTable() {
return valSecondaryTable;
}
public void setValSecondaryTable(String valSecondaryTable) {
this.valSecondaryTable = valSecondaryTable;
}
}
@Entity(name = "B")
@Table(name = B.TABLE)
@Inheritance(strategy = InheritanceType.JOINED)
static class B {
public static final String TABLE = "B_Table";
public B() {
}
public B(Long id) {
this.id = id;
}
@Id
private Long id;
private String valB;
public Long getId() {
return id;
}
public String getValB() {
return valB;
}
public void setValB(String valB) {
this.valB = valB;
}
}
@Entity(name = "SubB")
@Table(name = SubB.TABLE)
@SecondaryTable(name = SECONDARY_TABLE_NAME, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
static class SubB extends B {
public static final String TABLE = "SubB_table";
public SubB() {
}
public SubB(Long id) {
super( id );
}
private String valSubB;
@Column(table = SECONDARY_TABLE_NAME)
private String valSecondaryTable;
public String getValSubB() {
return valSubB;
}
public void setValSubB(String valSubB) {
this.valSubB = valSubB;
}
public String getValSecondaryTable() {
return valSecondaryTable;
}
public void setValSecondaryTable(String valSecondaryTable) {
this.valSecondaryTable = valSecondaryTable;
}
}
@Entity(name = "C")
@Table(name = C.TABLE)
static class C {
public static final String TABLE = "C_table";
@Id
@GeneratedValue
private Long id;
@ManyToOne
private SubA subA;
public Long getId() {
return id;
}
public SubA getSubA() {
return subA;
}
public void setSubA(SubA subA) {
this.subA = subA;
}
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.test.joinwithoutancestor;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
@TestForIssue(jiraKey = "HHH-12993")
public class OmitAncestorJoinWhenSecondaryTablePresentTest extends OmitAncestorTestCase {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { A.class, SubA.class, SubSubA.class };
}
@Test
public void test() {
assertFromTables( "select valSubASecondaryTable from SubA", SubA.TABLE, SubSubA.TABLE, SubA.SECONDARY_TABLE );
}
@Entity(name = "A")
@Table(name = A.TABLE)
@Inheritance(strategy = InheritanceType.JOINED)
static class A {
public static final String TABLE = "A_Table";
@Id
@GeneratedValue
private Long id;
private String valA;
public Long getId() {
return id;
}
public String getValA() {
return valA;
}
public void setValA(String valA) {
this.valA = valA;
}
}
@Entity(name = "SubA")
@Table(name = SubA.TABLE)
@SecondaryTable(name = SubA.SECONDARY_TABLE)
static class SubA extends A {
public static final String TABLE = "SubA_Table";
public static final String SECONDARY_TABLE = "SubA_Table_Sec";
private String valSubA;
@Column(table = SECONDARY_TABLE)
private String valSubASecondaryTable;
public String getValSubA() {
return valSubA;
}
public void setValSubA(String valSubA) {
this.valSubA = valSubA;
}
public String getValSubASecondaryTable() {
return valSubASecondaryTable;
}
public void setValSubASecondaryTable(String valSubASecondaryTable) {
this.valSubASecondaryTable = valSubASecondaryTable;
}
}
@Entity(name = "SubSubA")
@Table(name = SubSubA.TABLE)
static class SubSubA extends SubA {
public static final String TABLE = "SubSubA_Table";
private String valSubSubA;
public String getValSubSubA() {
return valSubSubA;
}
public void setValSubSubA(String valSubSubA) {
this.valSubSubA = valSubSubA;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.test.joinwithoutancestor;
import org.hibernate.Session;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.engine.query.spi.QueryPlanCache;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.internal.SessionImpl;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.transaction.TransactionUtil;
public abstract class OmitAncestorTestCase extends BaseCoreFunctionalTestCase {
protected void assertFromTables(String query, String... tables) {
try {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
String sql = getSql( session, query );
SqlAsserts.assertFromTables( sql, tables );
session.createQuery( query ).getResultList();
} );
}
catch (AssertionError e) {
throw e;
}
}
protected String getSql(Session session, String hql) {
// Create query
session.createQuery( hql );
// Get plan from cache
QueryPlanCache queryPlanCache = sessionFactory().getQueryPlanCache();
HQLQueryPlan hqlQueryPlan = queryPlanCache.getHQLQueryPlan(
hql,
false,
( (SessionImpl) session ).getLoadQueryInfluencers().getEnabledFilters()
);
QueryTranslator queryTranslator = hqlQueryPlan.getTranslators()[0];
String sql = queryTranslator.getSQLString();
return sql;
}
}

View File

@ -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.test.joinwithoutancestor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class SqlAsserts {
public static void assertFromTables(String sql, String... expectedTables) {
List<Table> actualTables = parse( sql );
if ( expectedTables.length == actualTables.size() ) {
boolean diffFound = false;
for ( int i = 0; i < expectedTables.length; i++ ) {
if ( !( expectedTables[i].equals( actualTables.get( i ).name ) ) ) {
diffFound = true;
break;
}
}
if ( !diffFound ) {
return;
}
}
List<String> actualTableNames = actualTables.stream().map( x -> x.name ).collect( Collectors.toList() );
List<String> expectedTableNames = Arrays.asList( expectedTables );
throw new AssertionError( "Expected tables: " + expectedTableNames + ", Actual tables: " + actualTableNames );
}
private static List<Table> parse(String sql) {
List<Table> result = new ArrayList<>();
String from = findFrom( sql );
List<String> commaSeparatedFromParts = findCommaSeparatedFromParts( from );
for ( String commaSeparatedFromPart : commaSeparatedFromParts ) {
List<Table> tables = findTables( commaSeparatedFromPart );
result.addAll( tables );
}
return result;
}
private static String findFrom(String sqlString) {
Pattern pattern = Pattern.compile( ".*\\s+from\\s+(?<frompart>.*?)(\\z|(\\s+(where|order|having).*))" );
Matcher matcher = pattern.matcher( sqlString );
if ( matcher.matches() ) {
return matcher.group( "frompart" );
}
else {
throw new RuntimeException( "Can not find from part in sql statement." );
}
}
private static List<String> findCommaSeparatedFromParts(String from) {
return Arrays.stream( from.split( "," ) ).map( x -> x.trim() ).collect( Collectors.toList() );
}
private static List<Table> findTables(String fromPart) {
List<Table> result = new ArrayList<>();
result.add( findFirstTable( fromPart ) );
String otherTablesPart = findOtherTablesPart( fromPart );
result.addAll( findOtherTables( otherTablesPart ) );
return result;
}
private static Table findFirstTable(String fromPart) {
Pattern pattern = Pattern.compile( "(?<table>\\S+)\\s+(?<alias>\\S*)\\s*(?<joins>.*)" );
Matcher matcher = pattern.matcher( fromPart );
if ( matcher.matches() ) {
Table firstTable = new Table( matcher.group( "table" ), matcher.group( "alias" ), false, false );
return firstTable;
}
else {
throw new RuntimeException( "Can not find the first table in the from part." );
}
}
private static String findOtherTablesPart(String fromPart) {
Pattern pattern = Pattern.compile( "(?<table>\\S+)\\s+(?<alias>\\S*)\\s*(?<joins>.*)" );
Matcher matcher = pattern.matcher( fromPart );
if ( matcher.matches() ) {
String joins = matcher.group( "joins" );
return joins;
}
else {
throw new RuntimeException( "Can not find joins in the from part." );
}
}
private static List<Table> findOtherTables(String otherTablesPart) {
Pattern pattern = Pattern.compile(
"(?<jointype>join|inner join|left join|cross join|left outer join)\\s+(?<table>\\S+)\\s+(?<alias>\\S+)" );
Matcher matcher = pattern.matcher( otherTablesPart );
List<Table> joins = new ArrayList<>();
while ( matcher.find() ) {
String table = matcher.group( "table" );
String alias = matcher.group( "alias" );
String join = matcher.group( "jointype" );
boolean innerJoin = join.equals( "join" ) || join.equals( "inner join" );
joins.add( new Table( table, alias, true, innerJoin ) );
}
return joins;
}
private static class Table {
String name;
String alias;
boolean join;
boolean innerJoin;
public Table(String table, String alias, boolean join, boolean innerJoin) {
this.name = table;
this.alias = alias;
this.join = join;
this.innerJoin = innerJoin;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
if ( innerJoin ) {
sb.append( "inner join " );
}
else if ( join ) {
sb.append( "join " );
}
sb.append( name );
sb.append( " " );
sb.append( alias );
return sb.toString();
}
}
}