HHH-12993 Omit joining of superclass table when querying subclass only
This commit is contained in:
parent
da847f4b57
commit
e0f4047429
|
@ -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
|
||||
|
||||
|
|
|
@ -447,4 +447,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
|
|||
public boolean isEnhancementAsProxyEnabled() {
|
||||
return delegate.isEnhancementAsProxyEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOmitJoinOfSuperclassTablesEnabled() {
|
||||
return delegate.isOmitJoinOfSuperclassTablesEnabled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -314,4 +314,6 @@ public interface SessionFactoryOptions {
|
|||
default boolean isEnhancementAsProxyEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isOmitJoinOfSuperclassTablesEnabled();
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -21,4 +21,6 @@ public interface TokenPrinters {
|
|||
|
||||
ASTPrinter ORDERBY_FRAGMENT_PRINTER = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class );
|
||||
|
||||
ASTPrinter REFERENCED_TABLES_PRINTER = new ASTReferencedTablesPrinter( SqlTokenTypes.class );
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
) );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue