org.hibernate.hql.internal.ast.tree.FromClause Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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 .
*/
package org.hibernate.hql.internal.ast.tree;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTIterator;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import antlr.SemanticException;
import antlr.collections.AST;
/**
* Represents the 'FROM' part of a query or subquery, containing all mapped class references.
*
* @author josh
*/
public class FromClause extends HqlSqlWalkerNode implements HqlSqlTokenTypes, DisplayableNode {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( FromClause.class );
public static final int ROOT_LEVEL = 1;
private int level = ROOT_LEVEL;
private Set fromElements = new LinkedHashSet();
private Map fromElementByClassAlias = new HashMap();
private Map fromElementByTableAlias = new HashMap();
private Map fromElementsByPath = new HashMap();
/**
* All of the implicit FROM xxx JOIN yyy elements that are the destination of a collection. These are created from
* index operators on collection property references.
*/
private Map collectionJoinFromElementsByPath = new HashMap();
/**
* Pointer to the parent FROM clause, if there is one.
*/
private FromClause parentFromClause;
/**
* Collection of FROM clauses of which this is the parent.
*/
private Set childFromClauses;
/**
* Counts the from elements as they are added.
*/
private int fromElementCounter;
/**
* Implied FROM elements to add onto the end of the FROM clause.
*/
private List impliedElements = new LinkedList();
/**
* Adds a new from element to the from node.
*
* @param path The reference to the class.
* @param alias The alias AST.
*
* @return FromElement - The new FROM element.
*/
public FromElement addFromElement(String path, AST alias) throws SemanticException {
// The path may be a reference to an alias defined in the parent query.
String classAlias = ( alias == null ) ? null : alias.getText();
checkForDuplicateClassAlias( classAlias );
FromElementFactory factory = new FromElementFactory( this, null, path, classAlias, null, false );
return factory.addFromElement();
}
void registerFromElement(FromElement element) {
fromElements.add( element );
String classAlias = element.getClassAlias();
if ( classAlias != null ) {
// The HQL class alias refers to the class name.
fromElementByClassAlias.put( classAlias, element );
}
// Associate the table alias with the element.
String tableAlias = element.getTableAlias();
if ( tableAlias != null ) {
fromElementByTableAlias.put( tableAlias, element );
}
}
void moveFromElementToEnd(FromElement element) {
fromElements.remove( element );
fromElements.add( element );
// We must move destinations which must come after the from element as well
for ( FromElement fromElement : element.getDestinations() ) {
if ( this == fromElement.getFromClause() ) {
fromElements.remove( fromElement );
fromElements.add( fromElement );
}
}
}
public void finishInit() {
// Insert the from elements into the AST in the same order as they were added to the HQL AST
FromElement lastFromElement = null;
for ( FromElement fromElement : fromElements ) {
if ( fromElement instanceof ComponentJoin ) {
// Component joins are no "real" joins, so they can't be put into the AST
continue;
}
fromElement.setFirstChild( null );
fromElement.setNextSibling( null );
if ( lastFromElement != null ) {
ASTUtil.appendChild( lastFromElement, fromElement );
}
lastFromElement = fromElement;
}
}
void addDuplicateAlias(String alias, FromElement element) {
if ( alias != null ) {
fromElementByClassAlias.put( alias, element );
}
}
private void checkForDuplicateClassAlias(String classAlias) throws SemanticException {
if ( classAlias != null && fromElementByClassAlias.containsKey( classAlias ) ) {
throw new SemanticException( "Duplicate definition of alias '" + classAlias + "'" );
}
}
/**
* Retrieve the from-element represented by the given alias.
*
* @param aliasOrClassName The alias by which to locate the from-element.
*
* @return The from-element assigned the given alias, or null if none.
*/
public FromElement getFromElement(String aliasOrClassName) {
FromElement fromElement = fromElementByClassAlias.get( aliasOrClassName );
if ( fromElement == null && getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() ) {
fromElement = findIntendedAliasedFromElementBasedOnCrazyJPARequirements( aliasOrClassName );
}
if ( fromElement == null && parentFromClause != null ) {
fromElement = parentFromClause.getFromElement( aliasOrClassName );
}
return fromElement;
}
public FromElement findFromElementBySqlAlias(String sqlAlias) {
FromElement fromElement = fromElementByTableAlias.get( sqlAlias );
if ( fromElement == null && parentFromClause != null ) {
fromElement = parentFromClause.getFromElement( sqlAlias );
}
return fromElement;
}
public FromElement findFromElementByUserOrSqlAlias(String userAlias, String sqlAlias) {
FromElement fromElement = null;
if ( userAlias != null ) {
fromElement = getFromElement( userAlias );
}
if ( fromElement == null ) {
fromElement = findFromElementBySqlAlias( sqlAlias );
}
return fromElement;
}
private FromElement findIntendedAliasedFromElementBasedOnCrazyJPARequirements(String specifiedAlias) {
for ( Map.Entry entry : fromElementByClassAlias.entrySet() ) {
final String alias = entry.getKey();
if ( alias.equalsIgnoreCase( specifiedAlias ) ) {
return entry.getValue();
}
}
return null;
}
/**
* Convenience method to check whether a given token represents a from-element alias.
*
* @param possibleAlias The potential from-element alias to check.
*
* @return True if the possibleAlias is an alias to a from-element visible
* from this point in the query graph.
*/
public boolean isFromElementAlias(String possibleAlias) {
boolean isAlias = containsClassAlias( possibleAlias );
if ( !isAlias && parentFromClause != null ) {
// try the parent FromClause...
isAlias = parentFromClause.isFromElementAlias( possibleAlias );
}
return isAlias;
}
/**
* Returns the list of from elements in order.
*
* @return the list of from elements (instances of FromElement).
*/
public List getFromElements() {
return ASTUtil.collectChildren( this, fromElementPredicate );
}
public FromElement getFromElement() {
// TODO: not sure about this one
// List fromElements = getFromElements();
// if ( fromElements == null || fromElements.isEmpty() ) {
// throw new QueryException( "Unable to locate from element" );
// }
return (FromElement) getFromElements().get( 0 );
}
/**
* Returns the list of from elements that will be part of the result set.
*
* @return the list of from elements that will be part of the result set.
*/
public List getProjectionList() {
return ASTUtil.collectChildren( this, projectionListPredicate );
}
public List getCollectionFetches() {
return ASTUtil.collectChildren( this, collectionFetchPredicate );
}
public boolean hasCollectionFecthes() {
return getCollectionFetches().size() > 0;
}
public List getExplicitFromElements() {
return ASTUtil.collectChildren( this, explicitFromPredicate );
}
private static ASTUtil.FilterPredicate fromElementPredicate = new ASTUtil.IncludePredicate() {
@Override
public boolean include(AST node) {
FromElement fromElement = (FromElement) node;
return fromElement.isFromOrJoinFragment();
}
};
private static ASTUtil.FilterPredicate projectionListPredicate = new ASTUtil.IncludePredicate() {
@Override
public boolean include(AST node) {
FromElement fromElement = (FromElement) node;
return fromElement.inProjectionList();
}
};
private static ASTUtil.FilterPredicate collectionFetchPredicate = new ASTUtil.IncludePredicate() {
@Override
public boolean include(AST node) {
FromElement fromElement = (FromElement) node;
return fromElement.isFetch() && fromElement.getQueryableCollection() != null;
}
};
private static ASTUtil.FilterPredicate explicitFromPredicate = new ASTUtil.IncludePredicate() {
@Override
public boolean include(AST node) {
final FromElement fromElement = (FromElement) node;
return !fromElement.isImplied();
}
};
FromElement findCollectionJoin(String path) {
return (FromElement) collectionJoinFromElementsByPath.get( path );
}
/**
* Look for an existing implicit or explicit join by the
* given path.
*/
FromElement findJoinByPath(String path) {
FromElement elem = findJoinByPathLocal( path );
if ( elem == null && parentFromClause != null ) {
elem = parentFromClause.findJoinByPath( path );
}
return elem;
}
FromElement findJoinByPathLocal(String path) {
Map joinsByPath = fromElementsByPath;
return (FromElement) joinsByPath.get( path );
}
void addJoinByPathMap(String path, FromElement destination) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "addJoinByPathMap() : %s -> %s", path, destination.getDisplayText() );
}
fromElementsByPath.put( path, destination );
}
/**
* Returns true if the from node contains the class alias name.
*
* @param alias The HQL class alias name.
*
* @return true if the from node contains the class alias name.
*/
public boolean containsClassAlias(String alias) {
boolean isAlias = fromElementByClassAlias.containsKey( alias );
if ( !isAlias && getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() ) {
isAlias = findIntendedAliasedFromElementBasedOnCrazyJPARequirements( alias ) != null;
}
return isAlias;
}
/**
* Returns true if the from node contains the table alias name.
*
* @param alias The SQL table alias name.
*
* @return true if the from node contains the table alias name.
*/
public boolean containsTableAlias(String alias) {
return fromElementByTableAlias.keySet().contains( alias );
}
public String getDisplayText() {
return "FromClause{" +
"level=" + level +
", fromElementCounter=" + fromElementCounter +
", fromElements=" + fromElements.size() +
", fromElementByClassAlias=" + fromElementByClassAlias.keySet() +
", fromElementByTableAlias=" + fromElementByTableAlias.keySet() +
", fromElementsByPath=" + fromElementsByPath.keySet() +
", collectionJoinFromElementsByPath=" + collectionJoinFromElementsByPath.keySet() +
", impliedElements=" + impliedElements +
"}";
}
public void setParentFromClause(FromClause parentFromClause) {
this.parentFromClause = parentFromClause;
if ( parentFromClause != null ) {
level = parentFromClause.getLevel() + 1;
parentFromClause.addChild( this );
}
}
private void addChild(FromClause fromClause) {
if ( childFromClauses == null ) {
childFromClauses = new HashSet();
}
childFromClauses.add( fromClause );
}
public FromClause locateChildFromClauseWithJoinByPath(String path) {
if ( childFromClauses != null && !childFromClauses.isEmpty() ) {
for ( FromClause child : childFromClauses ) {
if ( child.findJoinByPathLocal( path ) != null ) {
return child;
}
}
}
return null;
}
public void promoteJoin(FromElement elem) {
LOG.debugf( "Promoting [%s] to [%s]", elem, this );
//TODO: implement functionality
// this might be painful to do here, as the "join post processing" for
// the subquery has already been performed (meaning that for
// theta-join dialects, the join conditions have already been moved
// over to the where clause). A "simple" solution here might to
// doAfterTransactionCompletion "join post processing" once for the entire query (including
// any subqueries) at one fell swoop
}
public boolean isSubQuery() {
// TODO : this is broke for subqueries in statements other than selects...
return parentFromClause != null;
}
void addCollectionJoinFromElementByPath(String path, FromElement destination) {
LOG.debugf( "addCollectionJoinFromElementByPath() : %s -> %s", path, destination );
collectionJoinFromElementsByPath.put(
path,
destination
); // Add the new node to the map so that we don't create it twice.
}
public FromClause getParentFromClause() {
return parentFromClause;
}
public int getLevel() {
return level;
}
public int nextFromElementCounter() {
return fromElementCounter++;
}
public void resolve() {
// Make sure that all from elements registered with this FROM clause are actually in the AST.
ASTIterator iter = new ASTIterator( this.getFirstChild() );
Set childrenInTree = new HashSet();
while ( iter.hasNext() ) {
childrenInTree.add( iter.next() );
}
for ( FromElement fromElement : fromElements ) {
if ( !childrenInTree.contains( fromElement ) ) {
throw new IllegalStateException( "Element not in AST: " + fromElement );
}
}
}
public void addImpliedFromElement(FromElement element) {
impliedElements.add( element );
}
@Override
public String toString() {
return "FromClause{level=" + level + "}";
}
}