org.hibernate.hql.internal.ast.tree.DotNode.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.hql.internal.ast.tree.DotNode.java

Source

/*
 * 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.tree;

import org.hibernate.QueryException;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.hql.internal.CollectionProperties;
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.log.DeprecationLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.loader.plan.spi.EntityQuerySpace;
import org.hibernate.loader.plan.spi.QuerySpace;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

import antlr.SemanticException;
import antlr.collections.AST;

/**
 * Represents a reference to a property or alias expression.  This should duplicate the relevant behaviors in
 * PathExpressionParser.
 *
 * @author Joshua Davis
 */
public class DotNode extends FromReferenceNode implements DisplayableNode, SelectExpression {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(DotNode.class);

    ///////////////////////////////////////////////////////////////////////////
    // USED ONLY FOR REGRESSION TESTING!!!!
    //
    // todo : obviously get rid of all this junk ;)
    ///////////////////////////////////////////////////////////////////////////
    public static boolean useThetaStyleImplicitJoins;
    public static boolean regressionStyleJoinSuppression;

    public interface IllegalCollectionDereferenceExceptionBuilder {
        QueryException buildIllegalCollectionDereferenceException(String collectionPropertyName,
                FromReferenceNode lhs);
    }

    public static final IllegalCollectionDereferenceExceptionBuilder DEF_ILLEGAL_COLL_DEREF_EXCP_BUILDER = new IllegalCollectionDereferenceExceptionBuilder() {
        public QueryException buildIllegalCollectionDereferenceException(String propertyName,
                FromReferenceNode lhs) {
            String lhsPath = ASTUtil.getPathText(lhs);
            return new QueryException("illegal attempt to dereference collection [" + lhsPath
                    + "] with element property reference [" + propertyName + "]");
        }
    };
    public static IllegalCollectionDereferenceExceptionBuilder ILLEGAL_COLL_DEREF_EXCP_BUILDER = DEF_ILLEGAL_COLL_DEREF_EXCP_BUILDER;
    ///////////////////////////////////////////////////////////////////////////

    public static enum DereferenceType {
        UNKNOWN, ENTITY, COMPONENT, COLLECTION, PRIMITIVE, IDENTIFIER, JAVA_CONSTANT
    }

    /**
     * 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.
     */
    private String propertyPath;

    /**
     * The column names that this resolves to.
     */
    private String[] columns;

    /**
     * The type of join to create.   Default is an inner join.
     */
    private JoinType joinType = JoinType.INNER_JOIN;

    /**
     * Fetch join or not.
     */
    private boolean fetch;

    /**
     * The type of dereference that hapened
     */
    private DereferenceType dereferenceType = DereferenceType.UNKNOWN;

    private FromElement impliedJoin;

    /**
     * Sets the join type for this '.' node structure.
     *
     * @param joinType The type of join to use.
     *
     * @see org.hibernate.sql.JoinFragment
     */
    public void setJoinType(JoinType joinType) {
        this.joinType = joinType;
    }

    private String[] getColumns() throws QueryException {
        if (columns == null) {
            // Use the table fromElement and the property name to get the array of column names.
            String tableAlias = getLhs().getFromElement().getTableAlias();
            columns = getFromElement().toColumns(tableAlias, propertyPath, false);
        }
        return columns;
    }

    @Override
    public String getDisplayText() {
        StringBuilder buf = new StringBuilder();
        FromElement fromElement = getFromElement();
        buf.append("{propertyName=").append(propertyName);
        buf.append(",dereferenceType=").append(dereferenceType.name());
        buf.append(",getPropertyPath=").append(propertyPath);
        buf.append(",path=").append(getPath());
        if (fromElement != null) {
            buf.append(",tableAlias=").append(fromElement.getTableAlias());
            buf.append(",className=").append(fromElement.getClassName());
            buf.append(",classAlias=").append(fromElement.getClassAlias());
        } else {
            buf.append(",no from element");
        }
        buf.append('}');
        return buf.toString();
    }

    /**
     * Resolves the left hand side of the DOT.
     *
     * @throws SemanticException
     */
    @Override
    public void resolveFirstChild() throws SemanticException {
        FromReferenceNode lhs = (FromReferenceNode) getFirstChild();
        SqlNode property = (SqlNode) lhs.getNextSibling();

        // 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;
        }
        // Resolve the LHS fully, generate implicit joins.  Pass in the property name so that the resolver can
        // discover foreign key (id) properties.
        lhs.resolve(true, true, null, this);
        setFromElement(lhs.getFromElement()); // The 'from element' that the property is in.

        checkSubclassOrSuperclassPropertyReference(lhs, propName);
    }

    @Override
    public void resolveInFunctionCall(boolean generateJoin, boolean implicitJoin) throws SemanticException {
        if (isResolved()) {
            return;
        }
        Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.
        if (propertyType != null && propertyType.isCollectionType()) {
            resolveIndex(null);
        } else {
            resolveFirstChild();
            super.resolve(generateJoin, implicitJoin);
        }
    }

    public void resolveIndex(AST parent) throws SemanticException {
        if (isResolved()) {
            return;
        }
        Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.
        dereferenceCollection((CollectionType) propertyType, true, true, null, parent);
    }

    public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent,
            AST parentPredicate) throws SemanticException {
        // If this dot has already been resolved, stop now.
        if (isResolved()) {
            return;
        }

        Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.

        if (parent == null && AbstractEntityPersister.ENTITY_CLASS.equals(propertyName)) {
            DeprecationLogger.DEPRECATION_LOGGER.logDeprecationOfClassEntityTypeSelector(getLhs().getPath());
        }

        // If there is no data type for this node, and we're at the end of the path (top most dot node), then
        // this might be a Java constant.
        if (propertyType == null) {
            if (parent == null) {
                getWalker().getLiteralProcessor().lookupConstant(this);
            }
            // If the propertyType is null and there isn't a parent, just
            // stop now... there was a problem resolving the node anyway.
            return;
        }

        if (propertyType.isComponentType()) {
            // The property is a component...
            checkLhsIsNotCollection();
            dereferenceComponent(parent);
            initText();
        } else if (propertyType.isEntityType()) {
            // The property is another class..
            checkLhsIsNotCollection();
            dereferenceEntity((EntityType) propertyType, implicitJoin, classAlias, generateJoin, parent,
                    parentPredicate);
            initText();
        } else if (propertyType.isCollectionType()) {
            // The property is a collection...
            checkLhsIsNotCollection();
            dereferenceCollection((CollectionType) propertyType, implicitJoin, false, classAlias, parent);
        } else {
            // Otherwise, this is a primitive type.
            if (!CollectionProperties.isAnyCollectionProperty(propertyName)) {
                checkLhsIsNotCollection();
            }
            dereferenceType = DereferenceType.PRIMITIVE;
            initText();
        }
        setResolved();
    }

    private void initText() {
        String[] cols = getColumns();
        String text = String.join(", ", cols);
        boolean countDistinct = getWalker().isInCountDistinct() && getWalker().getSessionFactoryHelper()
                .getFactory().getDialect().requiresParensForTupleDistinctCounts();
        if (cols.length > 1 && (getWalker().isComparativeExpressionClause() || countDistinct)) {
            text = "(" + text + ")";
        }
        setText(text);
    }

    private Type prepareLhs() throws SemanticException {
        FromReferenceNode lhs = getLhs();
        lhs.prepareForDot(propertyName);
        return getDataType();
    }

    private void dereferenceCollection(CollectionType collectionType, boolean implicitJoin, boolean indexed,
            String classAlias, AST parent) throws SemanticException {

        dereferenceType = DereferenceType.COLLECTION;
        String role = collectionType.getRole();

        //foo.bars.size (also handles deprecated stuff like foo.bars.maxelement for backwardness)
        boolean isSizeProperty = getNextSibling() != null
                && CollectionProperties.isAnyCollectionProperty(getNextSibling().getText());

        if (isSizeProperty) {
            indexed = true; //yuck!
        }

        QueryableCollection queryableCollection = getSessionFactoryHelper().requireQueryableCollection(role);
        String propName = getPath();
        FromClause currentFromClause = getWalker().getCurrentFromClause();

        // If the lhs of the join is a "component join", we need to go back to the
        // first non-component-join as the origin to properly link aliases and
        // join columns
        FromElement lhsFromElement = getLhs().getFromElement();
        while (lhsFromElement != null && ComponentJoin.class.isInstance(lhsFromElement)) {
            lhsFromElement = lhsFromElement.getOrigin();
        }
        if (lhsFromElement == null) {
            throw new QueryException("Unable to locate appropriate lhs");
        }

        // determine whether we should use the table name or table alias to qualify the column names...
        // we need to use the table-name when:
        //      1) the top-level statement is not a SELECT
        //      2) the LHS FromElement is *the* FromElement from the top-level statement
        //
        // there is a caveat here.. if the update/delete statement are "multi-table" we should continue to use
        // the alias also, even if the FromElement is the root one...
        //
        // in all other cases, we should use the table alias
        if (getWalker().getStatementType() != SqlTokenTypes.SELECT) {
            if (isFromElementUpdateOrDeleteRoot(lhsFromElement)) {
                // at this point we know we have the 2 conditions above,
                // lets see if we have the mentioned "multi-table" caveat...
                boolean useAlias = false;
                if (getWalker().getStatementType() != SqlTokenTypes.INSERT) {
                    final Queryable persister = lhsFromElement.getQueryable();
                    if (persister.isMultiTable()) {
                        useAlias = true;
                    }
                }
                if (!useAlias) {
                    final String lhsTableName = lhsFromElement.getQueryable().getTableName();
                    columns = getFromElement().toColumns(lhsTableName, propertyPath, false, true);
                }
            }
        }

        // We do not look for an existing join on the same path, because
        // it makes sense to join twice on the same collection role
        FromElementFactory factory = new FromElementFactory(currentFromClause, lhsFromElement, propName, classAlias,
                getColumns(), implicitJoin);
        FromElement elem = factory.createCollection(queryableCollection, role, joinType, fetch, indexed);

        LOG.debugf("dereferenceCollection() : Created new FROM element for %s : %s", propName, elem);

        setImpliedJoin(elem);
        setFromElement(elem); // This 'dot' expression now refers to the resulting from element.

        if (isSizeProperty) {
            elem.setText("");
            elem.setUseWhereFragment(false);
        }

        if (!implicitJoin) {
            EntityPersister entityPersister = elem.getEntityPersister();
            if (entityPersister != null) {
                getWalker().addQuerySpaces(entityPersister.getQuerySpaces());
            }
        }
        getWalker().addQuerySpaces(queryableCollection.getCollectionSpaces()); // Always add the collection's query spaces.
    }

    private void dereferenceEntity(EntityType entityType, boolean implicitJoin, String classAlias,
            boolean generateJoin, AST parent, AST parentPredicate) throws SemanticException {
        checkForCorrelatedSubquery("dereferenceEntity");
        // three general cases we check here as to whether to render a physical SQL join:
        // 1) is our parent a DotNode as well?  If so, our property reference is
        //       being further de-referenced...
        // 2) is this a DML statement
        // 3) we were asked to generate any needed joins (generateJoins==true) *OR*
        //      we are currently processing a select or from clause
        // (an additional check is the regressionStyleJoinSuppression check solely intended for the test suite)
        //
        // The regressionStyleJoinSuppression is an additional check
        // intended solely for use within the test suite.  This forces the
        // implicit join resolution to behave more like the classic parser.
        // The underlying issue is that classic translator is simply wrong
        // about its decisions on whether or not to render an implicit join
        // into a physical SQL join in a lot of cases.  The piece it generally
        // tends to miss is that INNER joins effect the results by further
        // restricting the data set!  A particular manifestation of this is
        // the fact that the classic translator will skip the physical join
        // for ToOne implicit joins *if the query is shallow*; the result
        // being that Query.list() and Query.iterate() could return
        // different number of results!
        DotNode parentAsDotNode = null;
        String property = propertyName;
        final boolean joinIsNeeded;

        if (isDotNode(parent)) {
            // our parent is another dot node, meaning we are being further dereferenced.
            // thus we need to generate a join unless the parent refers to the associated
            // entity's PK (because 'our' table would know the FK).
            parentAsDotNode = (DotNode) parent;
            property = parentAsDotNode.propertyName;
            joinIsNeeded = generateJoin && !isPropertyEmbeddedInJoinProperties(parentAsDotNode.propertyName);
        } else if (!getWalker().isSelectStatement()) {
            // in non-select queries, the only time we should need to join is if we are in a subquery from clause
            joinIsNeeded = getWalker().getCurrentStatementType() == SqlTokenTypes.SELECT && getWalker().isInFrom();
        } else if (regressionStyleJoinSuppression) {
            // this is the regression style determination which matches the logic of the classic translator
            joinIsNeeded = generateJoin && (!getWalker().isInSelect() || !getWalker().isShallowQuery());
        } else if (parentPredicate != null) {
            // Never generate a join when we compare entities directly
            joinIsNeeded = generateJoin;
        } else {
            joinIsNeeded = generateJoin || (getWalker().isInSelect() || getWalker().isInFrom());
        }

        if (joinIsNeeded) {
            dereferenceEntityJoin(classAlias, entityType, implicitJoin, parent);
        } else {
            dereferenceEntityIdentifier(property, parentAsDotNode);
        }

    }

    private static boolean isDotNode(AST n) {
        return n != null && n.getType() == SqlTokenTypes.DOT;
    }

    private void dereferenceEntityJoin(String classAlias, EntityType propertyType, boolean impliedJoin, AST parent)
            throws SemanticException {
        dereferenceType = DereferenceType.ENTITY;
        if (LOG.isDebugEnabled()) {
            LOG.debugf("dereferenceEntityJoin() : generating join for %s in %s (%s) parent = %s", propertyName,
                    getFromElement().getClassName(), classAlias == null ? "<no alias>" : classAlias,
                    ASTUtil.getDebugString(parent));
        }
        // Create a new FROM node for the referenced class.
        String associatedEntityName = propertyType.getAssociatedEntityName();
        String tableAlias = getAliasGenerator().createName(associatedEntityName);

        String[] joinColumns = getColumns();
        String joinPath = getPath();

        if (impliedJoin && getWalker().isInFrom()) {
            joinType = getWalker().getImpliedJoinType();
        }

        FromClause currentFromClause = getWalker().getCurrentFromClause();
        FromElement elem = currentFromClause.findJoinByPath(joinPath);

        ///////////////////////////////////////////////////////////////////////////////
        //
        // This is the piece which recognizes the condition where an implicit join path
        // resolved earlier in a correlated subquery is now being referenced in the
        // outer query.  For 3.0final, we just let this generate a second join (which
        // is exactly how the old parser handles this).  Eventually we need to add this
        // logic back in and complete the logic in FromClause.promoteJoin; however,
        // FromClause.promoteJoin has its own difficulties (see the comments in
        // FromClause.promoteJoin).
        //
        //      if ( elem == null ) {
        //         // see if this joinPath has been used in a "child" FromClause, and if so
        //         // promote that element to the outer query
        //         FromClause currentNodeOwner = getFromElement().getFromClause();
        //         FromClause currentJoinOwner = currentNodeOwner.locateChildFromClauseWithJoinByPath( joinPath );
        //         if ( currentJoinOwner != null && currentNodeOwner != currentJoinOwner ) {
        //            elem = currentJoinOwner.findJoinByPathLocal( joinPath );
        //            if ( elem != null ) {
        //               currentFromClause.promoteJoin( elem );
        //               // EARLY EXIT!!!
        //               return;
        //            }
        //         }
        //      }
        //
        ///////////////////////////////////////////////////////////////////////////////

        boolean found = elem != null;
        // even though we might find a pre-existing element by join path, we may not be able to reuse it...
        boolean useFoundFromElement = found && canReuse(classAlias, elem);

        if (!useFoundFromElement) {
            // If the lhs of the join is a "component join", we need to go back to the
            // first non-component-join as the origin to properly link aliases and
            // join columns
            FromElement lhsFromElement = getLhs().getFromElement();
            while (lhsFromElement != null && ComponentJoin.class.isInstance(lhsFromElement)) {
                lhsFromElement = lhsFromElement.getOrigin();
            }
            if (lhsFromElement == null) {
                throw new QueryException("Unable to locate appropriate lhs");
            }

            String role = lhsFromElement.getClassName() + "." + propertyName;

            JoinSequence joinSequence;

            if (joinColumns.length == 0 && lhsFromElement instanceof EntityQuerySpace) {
                // When no columns are available, this is a special join that involves multiple subtypes
                String lhsTableAlias = getLhs().getFromElement().getTableAlias();

                AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister();

                String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath);

                // Special join sequence that uses the poly join columns
                joinSequence = getSessionFactoryHelper().createJoinSequence(impliedJoin, propertyType, tableAlias,
                        joinType, polyJoinColumns);
            } else {
                // If this is an implied join in a from element, then use the implied join type which is part of the
                // tree parser's state (set by the grammar actions).
                joinSequence = getSessionFactoryHelper().createJoinSequence(impliedJoin, propertyType, tableAlias,
                        joinType, joinColumns);
            }

            FromElementFactory factory = new FromElementFactory(currentFromClause, lhsFromElement, joinPath,
                    classAlias, joinColumns, impliedJoin);
            elem = factory.createEntityJoin(associatedEntityName, tableAlias, joinSequence, fetch,
                    getWalker().isInFrom(), propertyType, role, joinPath);
        } else {
            // NOTE : addDuplicateAlias() already performs nullness checks on the alias.
            currentFromClause.addDuplicateAlias(classAlias, elem);
        }
        setImpliedJoin(elem);
        getWalker().addQuerySpaces(elem.getEntityPersister().getQuerySpaces());
        setFromElement(elem); // This 'dot' expression now refers to the resulting from element.
    }

    private boolean canReuse(String classAlias, FromElement fromElement) {
        // if the from-clauses are the same, we can be a little more aggressive in terms of what we reuse
        if (fromElement.getFromClause() == getWalker().getCurrentFromClause()
                && areSame(classAlias, fromElement.getClassAlias())) {
            return true;
        }

        // otherwise (subquery case) dont reuse the fromElement if we are processing the from-clause of the subquery
        return getWalker().getCurrentClauseType() != SqlTokenTypes.FROM;
    }

    private boolean areSame(String alias1, String alias2) {
        // again, null != null here
        return !StringHelper.isEmpty(alias1) && !StringHelper.isEmpty(alias2) && alias1.equals(alias2);
    }

    private void setImpliedJoin(FromElement elem) {
        this.impliedJoin = elem;
        if (getFirstChild().getType() == SqlTokenTypes.DOT) {
            DotNode dotLhs = (DotNode) getFirstChild();
            if (dotLhs.getImpliedJoin() != null) {
                this.impliedJoin = dotLhs.getImpliedJoin();
            }
        }
    }

    @Override
    public FromElement getImpliedJoin() {
        return impliedJoin;
    }

    /**
     * Is the given property name a reference to the join key of the associated
     * entity constructed by the given entity type?
     * <p/>
     *
     * This method resolves the {@code propertyName} as a property of the entity type at the
     * {@link #propertyPath} relative to the {@link #getFromElement() FromElement}.
     * The implementation does so by invoking {@link FromElement#getPropertyType(String, String)},
     * which will resolve the property path against the entity's {@link org.hibernate.persister.entity.PropertyMapping}.
     * On initialization of the {@link EntityPersister}, this {@code PropertyMapping} is filled with
     * property paths for all the owned properties and associations, and (embedded) identifier or unique key
     * properties for owned associations.
     * Henceforth, whenever a property path is found in the {@code PropertyMapping} of the {@code EntityPersister}
     * of the {@code FromElement}, we know that the property corresponds to a SQL fragment producible from the
     * {@code FromElement}, and as such the entity property can be dereferenced (optimized) in the final query.
     *
     * <p/>
     * For example, consider a fragment like order.customer.id
     * (where order is a from-element alias).  Here, we'd have:
     * propertyName = "id" AND
     * propertyPath = "customer"
     * FromElement = Order
     * and are being asked to determine whether "customer.id" is a property path of Order
     *
     * @param propertyName The name of the property to check.
     *
     * @return True if propertyName references the entity's (owningType->associatedEntity)
     *         join key; false otherwise.
     */
    private boolean isPropertyEmbeddedInJoinProperties(String propertyName) {
        String propertyPath = String.join(".", this.propertyPath, propertyName);
        try {
            Type propertyType = getFromElement().getPropertyType(this.propertyPath, propertyPath);
            return propertyType != null;
        } catch (QueryException e) {
            return false;
        }
    }

    private void checkForCorrelatedSubquery(String methodName) {
        if (isCorrelatedSubselect()) {
            LOG.debugf("%s() : correlated subquery", methodName);
        }
    }

    private boolean isCorrelatedSubselect() {
        return getWalker().isSubQuery() && getFromElement().getFromClause() != getWalker().getCurrentFromClause();
    }

    private void checkLhsIsNotCollection() throws SemanticException {
        if (getLhs().getDataType() != null && getLhs().getDataType().isCollectionType()) {
            throw ILLEGAL_COLL_DEREF_EXCP_BUILDER.buildIllegalCollectionDereferenceException(propertyName,
                    getLhs());
        }
    }

    private void dereferenceComponent(AST parent) {
        dereferenceType = DereferenceType.COMPONENT;
        setPropertyNameAndPath(parent);
    }

    private void dereferenceEntityIdentifier(String propertyName, DotNode dotParent) {
        // special shortcut for id properties, skip the join!
        // this must only occur at the _end_ of a path expression
        if (LOG.isDebugEnabled()) {
            LOG.debugf("dereferenceShortcut() : property %s in %s does not require a join.", propertyName,
                    getFromElement().getClassName());
        }

        setPropertyNameAndPath(dotParent); // Set the unresolved path in this node and the parent.
        initText();

        // Set the text for the parent.
        if (dotParent != null) {
            dotParent.dereferenceType = DereferenceType.IDENTIFIER;
            dotParent.setText(getText());
            dotParent.columns = getColumns();
        }
    }

    private void setPropertyNameAndPath(AST parent) {
        if (isDotNode(parent)) {
            DotNode dotNode = (DotNode) parent;
            AST lhs = dotNode.getFirstChild();
            AST rhs = lhs.getNextSibling();
            propertyName = rhs.getText();
            propertyPath = propertyPath + "." + propertyName; // Append the new property name onto the unresolved path.
            dotNode.propertyPath = propertyPath;
            LOG.debugf("Unresolved property path is now '%s'", dotNode.propertyPath);
        } else {
            LOG.debugf("Terminal getPropertyPath = [%s]", propertyPath);
        }
    }

    @Override
    public Type getDataType() {
        if (super.getDataType() == null) {
            FromElement fromElement = getLhs().getFromElement();
            if (fromElement == null) {
                return null;
            }
            // If the lhs is a collection, use CollectionPropertyMapping
            Type propertyType = fromElement.getPropertyType(propertyPath, propertyPath);
            LOG.debugf("getDataType() : %s -> %s", propertyPath, propertyType);
            super.setDataType(propertyType);
        }
        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;
    }

    public String getPropertyPath() {
        return propertyPath;
    }

    public String getPropertyName() {
        return propertyName;
    }

    public String getOriginalPropertyName() {
        return originalPropertyName;
    }

    public FromReferenceNode getLhs() {
        FromReferenceNode lhs = ((FromReferenceNode) getFirstChild());
        if (lhs == null) {
            throw new IllegalStateException("DOT node with no left-hand-side!");
        }
        return lhs;
    }

    /**
     * Returns the full path of the node.
     *
     * @return the full path of the node.
     */
    @Override
    public String getPath() {
        if (path == null) {
            FromReferenceNode lhs = getLhs();
            if (lhs == null) {
                path = getText();
            } else {
                SqlNode rhs = (SqlNode) lhs.getNextSibling();
                path = lhs.getPath() + "." + rhs.getOriginalText();
            }
        }
        return path;
    }

    public void setFetch(boolean fetch) {
        this.fetch = fetch;
    }

    public void setScalarColumnText(int i) throws SemanticException {
        String[] sqlColumns = getColumns();
        ColumnHelper.generateScalarColumns(this, sqlColumns, i);
    }

    /**
     * Special method to resolve expressions in the SELECT list.
     *
     * @throws SemanticException if this cannot be resolved.
     */
    public void resolveSelectExpression() throws SemanticException {
        if (getWalker().isShallowQuery() || getWalker().getCurrentFromClause().isSubQuery()) {
            resolve(false, true);
        } else {
            resolve(true, false);
            Type type = getDataType();
            if (type.isEntityType()) {
                FromElement fromElement = getFromElement();
                fromElement.setIncludeSubclasses(true); // Tell the destination fromElement to 'includeSubclasses'.
                if (useThetaStyleImplicitJoins) {
                    fromElement.getJoinSequence().setUseThetaStyle(true); // Use theta style (for regression)
                    // Move the node up, after the origin node.
                    FromElement origin = fromElement.getOrigin();
                    if (origin != null) {
                        ASTUtil.makeSiblingOfParent(origin, fromElement);
                    }
                }
            }
        }

        FromReferenceNode lhs = getLhs();
        while (lhs != null) {
            checkSubclassOrSuperclassPropertyReference(lhs, lhs.getNextSibling().getText());
            lhs = (FromReferenceNode) lhs.getFirstChild();
        }
    }

    public void setResolvedConstant(String text) {
        path = text;
        dereferenceType = DereferenceType.JAVA_CONSTANT;
        setResolved(); // Don't resolve the node again.
    }

    private boolean checkSubclassOrSuperclassPropertyReference(FromReferenceNode lhs, String propertyName) {
        if (lhs != null && !(lhs instanceof IndexNode)) {
            final FromElement source = lhs.getFromElement();
            if (source != null) {
                source.handlePropertyBeingDereferenced(lhs.getDataType(), propertyName);
            }
        }
        return false;
    }
}