org.hibernate.hql.internal.ast.HqlSqlWalker.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.hql.internal.ast.HqlSqlWalker.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;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.engine.internal.ParameterBinder;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.CollectionProperties;
import org.hibernate.hql.internal.antlr.HqlSqlBaseWalker;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.antlr.HqlTokenTypes;
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
import org.hibernate.hql.internal.ast.tree.AggregateNode;
import org.hibernate.hql.internal.ast.tree.AssignmentSpecification;
import org.hibernate.hql.internal.ast.tree.CastFunctionNode;
import org.hibernate.hql.internal.ast.tree.CollectionFunction;
import org.hibernate.hql.internal.ast.tree.ConstructorNode;
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
import org.hibernate.hql.internal.ast.tree.DotNode;
import org.hibernate.hql.internal.ast.tree.EntityJoinFromElement;
import org.hibernate.hql.internal.ast.tree.FromClause;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.FromElementFactory;
import org.hibernate.hql.internal.ast.tree.FromReferenceNode;
import org.hibernate.hql.internal.ast.tree.IdentNode;
import org.hibernate.hql.internal.ast.tree.IndexNode;
import org.hibernate.hql.internal.ast.tree.InsertStatement;
import org.hibernate.hql.internal.ast.tree.IntoClause;
import org.hibernate.hql.internal.ast.tree.MethodNode;
import org.hibernate.hql.internal.ast.tree.OperatorNode;
import org.hibernate.hql.internal.ast.tree.ParameterContainer;
import org.hibernate.hql.internal.ast.tree.ParameterNode;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.ast.tree.ResolvableNode;
import org.hibernate.hql.internal.ast.tree.RestrictableStatement;
import org.hibernate.hql.internal.ast.tree.ResultVariableRefNode;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.hql.internal.ast.tree.SelectExpression;
import org.hibernate.hql.internal.ast.tree.UpdateStatement;
import org.hibernate.hql.internal.ast.util.ASTPrinter;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.hql.internal.ast.util.AliasGenerator;
import org.hibernate.hql.internal.ast.util.JoinProcessor;
import org.hibernate.hql.internal.ast.util.LiteralProcessor;
import org.hibernate.hql.internal.ast.util.NodeTraverser;
import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
import org.hibernate.hql.internal.ast.util.SyntheticAndFactory;
import org.hibernate.hql.internal.ast.util.TokenPrinters;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
import org.hibernate.id.IdentifierGenerator;
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.internal.util.collections.ArrayHelper;
import org.hibernate.param.CollectionFilterKeyParameterSpecification;
import org.hibernate.param.NamedParameterSpecification;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.param.PositionalParameterSpecification;
import org.hibernate.param.VersionTypeSeedParameterSpecification;
import org.hibernate.persister.collection.CollectionPropertyNames;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.DbTimestampType;
import org.hibernate.type.Type;
import org.hibernate.type.VersionType;
import org.hibernate.usertype.UserVersionType;

import antlr.ASTFactory;
import antlr.RecognitionException;
import antlr.SemanticException;
import antlr.collections.AST;

import static org.hibernate.hql.spi.QueryTranslator.ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED;

/**
 * Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase).
 * <ul>
 * <li>Isolates the Hibernate API-specific code from the ANTLR generated code.</li>
 * <li>Handles the SQL fragments generated by the persisters in order to create the SELECT and FROM clauses,
 * taking into account the joins and projections that are implied by the mappings (persister/queryable).</li>
 * <li>Uses SqlASTFactory to create customized AST nodes.</li>
 * </ul>
 *
 * @see SqlASTFactory
 */
public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, ParameterBinder.NamedParameterSource {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(HqlSqlWalker.class);

    private final QueryTranslatorImpl queryTranslatorImpl;
    private final HqlParser hqlParser;
    private final SessionFactoryHelper sessionFactoryHelper;
    private final Map tokenReplacements;
    private final AliasGenerator aliasGenerator = new AliasGenerator();
    private final LiteralProcessor literalProcessor;
    private final ParseErrorHandler parseErrorHandler;
    private final String collectionFilterRole;

    private FromClause currentFromClause;
    private SelectClause selectClause;

    /**
     * Maps each top-level result variable to its SelectExpression;
     * (excludes result variables defined in subqueries)
     */
    private Map<String, SelectExpression> selectExpressionsByResultVariable = new HashMap<>();

    private Set<Serializable> querySpaces = new HashSet<>();

    private int parameterCount;
    private Map namedParameters;
    private Map positionalParameters;

    private ArrayList<ParameterSpecification> parameterSpecs = new ArrayList<>();
    private int numberOfParametersInSetClause;

    private ArrayList assignmentSpecifications = new ArrayList();

    private JoinType impliedJoinType = JoinType.INNER_JOIN;

    private boolean inEntityGraph;

    /**
     * Create a new tree transformer.
     *
     * @param qti Back pointer to the query translator implementation that is using this tree transform.
     * @param sfi The session factory implementor where the Hibernate mappings can be found.
     * @param parser A reference to the phase-1 parser
     * @param tokenReplacements Registers the token replacement map with the walker.  This map will
     * be used to substitute function names and constants.
     * @param collectionRole The collection role name of the collection used as the basis for the
     * filter, NULL if this is not a collection filter compilation.
     */
    public HqlSqlWalker(QueryTranslatorImpl qti, SessionFactoryImplementor sfi, HqlParser parser,
            Map tokenReplacements, String collectionRole) {
        setASTFactory(new SqlASTFactory(this));
        // Initialize the error handling delegate.
        this.parseErrorHandler = new ErrorTracker(qti.getQueryString());
        this.queryTranslatorImpl = qti;
        this.sessionFactoryHelper = new SessionFactoryHelper(sfi);
        this.literalProcessor = new LiteralProcessor(this);
        this.tokenReplacements = tokenReplacements;
        this.collectionFilterRole = collectionRole;
        this.hqlParser = parser;
    }

    // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private int traceDepth;

    @Override
    public void traceIn(String ruleName, AST tree) {
        if (!LOG.isTraceEnabled()) {
            return;
        }
        if (inputState.guessing > 0) {
            return;
        }
        String prefix = StringHelper.repeat('-', (traceDepth++ * 2)) + "-> ";
        String traceText = ruleName + " (" + buildTraceNodeName(tree) + ")";
        LOG.trace(prefix + traceText);
    }

    private String buildTraceNodeName(AST tree) {
        return tree == null ? "???"
                : tree.getText() + " [" + TokenPrinters.SQL_TOKEN_PRINTER.getTokenTypeName(tree.getType()) + "]";
    }

    @Override
    public void traceOut(String ruleName, AST tree) {
        if (!LOG.isTraceEnabled()) {
            return;
        }
        if (inputState.guessing > 0) {
            return;
        }
        String prefix = "<-" + StringHelper.repeat('-', (--traceDepth * 2)) + " ";
        LOG.trace(prefix + ruleName);
    }

    @Override
    protected void prepareFromClauseInputTree(AST fromClauseInput) {
        if (!isSubQuery()) {
            //         // inject param specifications to account for dynamic filter param values
            //         if ( ! getEnabledFilters().isEmpty() ) {
            //            Iterator filterItr = getEnabledFilters().values().iterator();
            //            while ( filterItr.hasNext() ) {
            //               FilterImpl filter = ( FilterImpl ) filterItr.next();
            //               if ( ! filter.getFilterDefinition().getParameterNames().isEmpty() ) {
            //                  Iterator paramItr = filter.getFilterDefinition().getParameterNames().iterator();
            //                  while ( paramItr.hasNext() ) {
            //                     String parameterName = ( String ) paramItr.next();
            //                     // currently param filters *only* work with single-column parameter types;
            //                     // if that limitation is ever lifted, this logic will need to change to account for that
            //                     ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
            //                     DynamicFilterParameterSpecification paramSpec = new DynamicFilterParameterSpecification(
            //                           filter.getName(),
            //                           parameterName,
            //                           filter.getFilterDefinition().getParameterType( parameterName ),
            //                            positionalParameterCount++
            //                     );
            //                     collectionFilterKeyParameter.setHqlParameterSpecification( paramSpec );
            //                     parameterSpecs.add( paramSpec );
            //                  }
            //               }
            //            }
            //         }

            if (isFilter()) {
                // Handle collection-filter compilation.
                // IMPORTANT NOTE: This is modifying the INPUT (HQL) tree, not the output tree!
                QueryableCollection persister = sessionFactoryHelper.getCollectionPersister(collectionFilterRole);
                Type collectionElementType = persister.getElementType();
                if (!collectionElementType.isEntityType()) {
                    throw new QueryException("collection of values in filter: this");
                }

                String collectionElementEntityName = persister.getElementPersister().getEntityName();
                ASTFactory inputAstFactory = hqlParser.getASTFactory();
                AST fromElement = inputAstFactory.create(HqlTokenTypes.FILTER_ENTITY, collectionElementEntityName);
                ASTUtil.createSibling(inputAstFactory, HqlTokenTypes.ALIAS, "this", fromElement);
                fromClauseInput.addChild(fromElement);
                // Show the modified AST.
                LOG.debug("prepareFromClauseInputTree() : Filter - Added 'this' as a from element...");
                queryTranslatorImpl.showHqlAst(hqlParser.getAST());

                // Create a parameter specification for the collection filter...
                final Type collectionFilterKeyType = sessionFactoryHelper
                        .requireQueryableCollection(collectionFilterRole).getKeyType();
                final ParameterNode collectionFilterKeyParameter = (ParameterNode) astFactory.create(PARAM, "?");
                final CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification(
                        collectionFilterRole, collectionFilterKeyType);
                parameterCount++;
                collectionFilterKeyParameter.setHqlParameterSpecification(collectionFilterKeyParameterSpec);
                parameterSpecs.add(collectionFilterKeyParameterSpec);
            }
        }
    }

    public boolean isFilter() {
        return collectionFilterRole != null;
    }

    public String getCollectionFilterRole() {
        return collectionFilterRole;
    }

    public boolean isInEntityGraph() {
        return inEntityGraph;
    }

    public SessionFactoryHelper getSessionFactoryHelper() {
        return sessionFactoryHelper;
    }

    public Map getTokenReplacements() {
        return tokenReplacements;
    }

    public AliasGenerator getAliasGenerator() {
        return aliasGenerator;
    }

    public FromClause getCurrentFromClause() {
        return currentFromClause;
    }

    public ParseErrorHandler getParseErrorHandler() {
        return parseErrorHandler;
    }

    @Override
    public void reportError(RecognitionException e) {
        parseErrorHandler.reportError(e); // Use the delegate.
    }

    @Override
    public void reportError(String s) {
        parseErrorHandler.reportError(s); // Use the delegate.
    }

    @Override
    public void reportWarning(String s) {
        parseErrorHandler.reportWarning(s);
    }

    /**
     * Returns the set of unique query spaces (a.k.a.
     * table names) that occurred in the query.
     *
     * @return A set of table names (Strings).
     */
    public Set<Serializable> getQuerySpaces() {
        return querySpaces;
    }

    @Override
    protected AST createFromElement(String path, AST alias, AST propertyFetch) throws SemanticException {
        FromElement fromElement = currentFromClause.addFromElement(path, alias);
        fromElement.setAllPropertyFetch(propertyFetch != null);
        return fromElement;
    }

    @Override
    protected AST createFromFilterElement(AST filterEntity, AST alias) throws SemanticException {
        FromElement fromElement = currentFromClause.addFromElement(filterEntity.getText(), alias);
        FromClause fromClause = fromElement.getFromClause();
        QueryableCollection persister = sessionFactoryHelper.getCollectionPersister(collectionFilterRole);
        // Get the names of the columns used to link between the collection
        // owner and the collection elements.
        String[] keyColumnNames = persister.getKeyColumnNames();
        String fkTableAlias = persister.isOneToMany() ? fromElement.getTableAlias()
                : fromClause.getAliasGenerator().createName(collectionFilterRole);
        JoinSequence join = sessionFactoryHelper.createJoinSequence();
        join.setRoot(persister, fkTableAlias);
        if (!persister.isOneToMany()) {
            join.addJoin((AssociationType) persister.getElementType(), fromElement.getTableAlias(),
                    JoinType.INNER_JOIN, persister.getElementColumnNames(fkTableAlias));
        }
        join.addCondition(fkTableAlias, keyColumnNames, " = ?");
        fromElement.setJoinSequence(join);
        fromElement.setFilter(true);
        LOG.debug("createFromFilterElement() : processed filter FROM element.");
        return fromElement;
    }

    @Override
    protected void createFromJoinElement(AST path, AST alias, int joinType, AST fetchNode, AST propertyFetch,
            AST with) throws SemanticException {
        boolean fetch = fetchNode != null;
        if (fetch && isSubQuery()) {
            throw new QueryException("fetch not allowed in subquery from-elements");
        }

        // the incoming "path" can be either:
        //      1) an implicit join path (join p.address.city)
        //       2) an entity-join (join com.acme.User)
        //
        // so make the proper interpretation here...

        final EntityPersister entityJoinReferencedPersister = resolveEntityJoinReferencedPersister(path);
        if (entityJoinReferencedPersister != null) {
            // `path` referenced an entity
            final EntityJoinFromElement join = createEntityJoin(entityJoinReferencedPersister, alias, joinType,
                    propertyFetch, with);

            ((FromReferenceNode) path).setFromElement(join);
        } else {
            if (path.getType() != SqlTokenTypes.DOT) {
                throw new SemanticException("Path expected for join!");
            }

            DotNode dot = (DotNode) path;
            JoinType hibernateJoinType = JoinProcessor.toHibernateJoinType(joinType);
            dot.setJoinType(hibernateJoinType); // Tell the dot node about the join type.
            dot.setFetch(fetch);
            // Generate an explicit join for the root dot node.   The implied joins will be collected and passed up
            // to the root dot node.
            dot.resolve(true, false, alias == null ? null : alias.getText());

            final FromElement fromElement;
            if (dot.getDataType() != null && dot.getDataType().isComponentType()) {
                if (dot.getDataType().isAnyType()) {
                    throw new SemanticException("An AnyType attribute cannot be join fetched");
                    // ^^ because the discriminator (aka, the "meta columns") must be known to the SQL in
                    //       a non-parameterized way.
                }
                FromElementFactory factory = new FromElementFactory(getCurrentFromClause(),
                        dot.getLhs().getFromElement(), dot.getPropertyPath(),
                        alias == null ? null : alias.getText(), null, false);
                fromElement = factory.createComponentJoin((CompositeType) dot.getDataType());
            } else {
                fromElement = dot.getImpliedJoin();
                fromElement.setAllPropertyFetch(propertyFetch != null);

                if (with != null) {
                    if (fetch) {
                        throw new SemanticException("with-clause not allowed on fetched associations; use filters");
                    }
                    handleWithFragment(fromElement, with);
                }
            }

            if (LOG.isDebugEnabled()) {
                LOG.debug("createFromJoinElement() : "
                        + getASTPrinter().showAsString(fromElement, "-- join tree --"));
            }
        }
    }

    private EntityPersister resolveEntityJoinReferencedPersister(AST path) {
        if (path.getType() == IDENT) {
            final IdentNode pathIdentNode = (IdentNode) path;
            String name = path.getText();
            if (name == null) {
                name = pathIdentNode.getOriginalText();
            }
            return sessionFactoryHelper.findEntityPersisterByName(name);
        } else if (path.getType() == DOT) {
            final String pathText = ASTUtil.getPathText(path);
            return sessionFactoryHelper.findEntityPersisterByName(pathText);
        }
        return null;
    }

    @Override
    protected void finishFromClause(AST fromClause) throws SemanticException {
        ((FromClause) fromClause).finishInit();
    }

    private EntityJoinFromElement createEntityJoin(EntityPersister entityPersister, AST aliasNode, int joinType,
            AST propertyFetch, AST with) throws SemanticException {
        final String alias = aliasNode == null ? null : aliasNode.getText();
        LOG.debugf("Creating entity-join FromElement [%s -> %s]", alias, entityPersister.getEntityName());
        EntityJoinFromElement join = new EntityJoinFromElement(this, getCurrentFromClause(), entityPersister,
                JoinProcessor.toHibernateJoinType(joinType), propertyFetch != null, alias);

        if (with != null) {
            handleWithFragment(join, with);
        }

        return join;
    }

    private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException {
        try {
            withClause(hqlWithNode);
            AST hqlSqlWithNode = returnAST;
            if (LOG.isDebugEnabled()) {
                LOG.debug("handleWithFragment() : "
                        + getASTPrinter().showAsString(hqlSqlWithNode, "-- with clause --"));
            }
            WithClauseVisitor visitor = new WithClauseVisitor(fromElement, queryTranslatorImpl);
            NodeTraverser traverser = new NodeTraverser(visitor);
            traverser.traverseDepthFirst(hqlSqlWithNode);

            SqlGenerator sql = new SqlGenerator(getSessionFactoryHelper().getFactory());
            sql.whereExpr(hqlSqlWithNode.getFirstChild());

            fromElement.setWithClauseFragment(hqlSqlWithNode.getFirstChild(), "(" + sql.getSQL() + ")");
        } catch (SemanticException e) {
            throw e;
        } catch (InvalidWithClauseException e) {
            throw e;
        } catch (Exception e) {
            throw new SemanticException(e.getMessage());
        }
    }

    private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy {
        private final FromElement joinFragment;
        private final QueryTranslatorImpl queryTranslatorImpl;

        private FromElement referencedFromElement;
        private String joinAlias;

        public WithClauseVisitor(FromElement fromElement, QueryTranslatorImpl queryTranslatorImpl) {
            this.joinFragment = fromElement;
            this.queryTranslatorImpl = queryTranslatorImpl;
        }

        public void visit(AST node) {
            // TODO : currently expects that the individual with expressions apply to the same sql table join.
            //      This may not be the case for joined-subclass where the property values
            //      might be coming from different tables in the joined hierarchy.  At some
            //      point we should expand this to support that capability.  However, that has
            //      some difficulties:
            //          1) the biggest is how to handle ORs when the individual comparisons are
            //              linked to different sql joins.
            //          2) here we would need to track each comparison individually, along with
            //              the join alias to which it applies and then pass that information
            //              back to the FromElement so it can pass it along to the JoinSequence
            if (node instanceof DotNode) {
                DotNode dotNode = (DotNode) node;
                FromElement fromElement = dotNode.getFromElement();
                if (referencedFromElement != null) {
                    //               if ( fromElement != referencedFromElement ) {
                    //                  throw new HibernateException( "with-clause referenced two different from-clause elements" );
                    //               }
                } else {
                    referencedFromElement = fromElement;
                    joinAlias = extractAppliedAlias(dotNode);
                    // TODO : temporary
                    //      needed because currently persister is the one that
                    // creates and renders the join fragments for inheritance
                    //      hierarchies...
                    //               if ( !joinAlias.equals( referencedFromElement.getTableAlias() ) ) {
                    //                  throw new InvalidWithClauseException(
                    //                        "with clause can only reference columns in the driving table",
                    //                        queryTranslatorImpl.getQueryString()
                    //                  );
                    //               }
                }
            } else if (node instanceof ParameterNode) {
                applyParameterSpecification(((ParameterNode) node).getHqlParameterSpecification());
            } else if (node instanceof ParameterContainer) {
                applyParameterSpecifications((ParameterContainer) node);
            }
        }

        private void applyParameterSpecifications(ParameterContainer parameterContainer) {
            if (parameterContainer.hasEmbeddedParameters()) {
                ParameterSpecification[] specs = parameterContainer.getEmbeddedParameters();
                for (ParameterSpecification spec : specs) {
                    applyParameterSpecification(spec);
                }
            }
        }

        private void applyParameterSpecification(ParameterSpecification paramSpec) {
            joinFragment.addEmbeddedParameter(paramSpec);
        }

        private String extractAppliedAlias(DotNode dotNode) {
            return dotNode.getText().substring(0, dotNode.getText().indexOf('.'));
        }

        public FromElement getReferencedFromElement() {
            return referencedFromElement;
        }

        public String getJoinAlias() {
            return joinAlias;
        }
    }

    /**
     * Sets the current 'FROM' context.
     *
     * @param fromNode The new 'FROM' context.
     * @param inputFromNode The from node from the input AST.
     */
    @Override
    protected void pushFromClause(AST fromNode, AST inputFromNode) {
        FromClause newFromClause = (FromClause) fromNode;
        newFromClause.setParentFromClause(currentFromClause);
        currentFromClause = newFromClause;
    }

    /**
     * Returns to the previous 'FROM' context.
     */
    private void popFromClause() {
        currentFromClause = currentFromClause.getParentFromClause();
    }

    @Override
    protected void lookupAlias(AST aliasRef) throws SemanticException {
        FromElement alias = currentFromClause.getFromElement(aliasRef.getText());
        FromReferenceNode aliasRefNode = (FromReferenceNode) aliasRef;
        aliasRefNode.setFromElement(alias);
    }

    @Override
    protected void setImpliedJoinType(int joinType) {
        impliedJoinType = JoinProcessor.toHibernateJoinType(joinType);
    }

    public JoinType getImpliedJoinType() {
        return impliedJoinType;
    }

    @Override
    protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
        DotNode dotNode = (DotNode) dot;
        FromReferenceNode lhs = dotNode.getLhs();
        AST rhs = lhs.getNextSibling();

        // this used to be a switch statement based on the rhs's node type
        //      expecting it to be SqlTokenTypes.ELEMENTS or
        //      SqlTokenTypes.INDICES in the cases where the re-arranging is needed
        //
        // In such cases it additionally expects the RHS to be a CollectionFunction node.
        //
        // However, in my experience these assumptions sometimes did not works as sometimes the node
        //       types come in with the node type WEIRD_IDENT.  What this does now is to:
        //         1) see if the LHS is a collection
        //         2) see if the RHS is a reference to one of the "collection properties".
        //      if both are true, we log a deprecation warning
        if (lhs.getDataType() != null && lhs.getDataType().isCollectionType()) {
            if (CollectionProperties.isCollectionProperty(rhs.getText())) {
                DeprecationLogger.DEPRECATION_LOGGER.logDeprecationOfCollectionPropertiesInHql(rhs.getText(),
                        lhs.getPath());
            }

            // perform the re-arrangement
            if (CollectionPropertyNames.COLLECTION_INDICES.equalsIgnoreCase(rhs.getText())
                    || CollectionPropertyNames.COLLECTION_ELEMENTS.equalsIgnoreCase(rhs.getText())) {
                if (LOG.isDebugEnabled()) {
                    LOG.debugf("lookupProperty() %s => %s(%s)", dotNode.getPath(), rhs.getText(), lhs.getPath());
                }

                final CollectionFunction f;
                if (rhs instanceof CollectionFunction) {
                    f = (CollectionFunction) rhs;
                } else {
                    f = new CollectionFunction();
                    f.initialize(SqlTokenTypes.METHOD_CALL, rhs.getText());
                    f.initialize(this);
                }

                // Re-arrange the tree so that the collection function is the root and the lhs is the path.
                f.setFirstChild(lhs);
                lhs.setNextSibling(null);
                dotNode.setFirstChild(f);
                resolve(lhs); // Don't forget to resolve the argument!
                f.resolve(inSelect); // Resolve the collection function now.
                return f;
            }
        }

        // otherwise, resolve the path and return it
        dotNode.resolveFirstChild();
        return dotNode;
    }

    @Override
    protected boolean isNonQualifiedPropertyRef(AST ident) {
        final String identText = ident.getText();
        if (currentFromClause.isFromElementAlias(identText)) {
            return false;
        }

        List fromElements = currentFromClause.getExplicitFromElements();
        if (fromElements.size() == 1) {
            final FromElement fromElement = (FromElement) fromElements.get(0);
            try {
                LOG.tracev("Attempting to resolve property [{0}] as a non-qualified ref", identText);
                return fromElement.getPropertyMapping(identText).toType(identText) != null;
            } catch (QueryException e) {
                // Should mean that no such property was found
            }
        }

        return false;
    }

    @Override
    protected AST lookupNonQualifiedProperty(AST property) throws SemanticException {
        final FromElement fromElement = (FromElement) currentFromClause.getExplicitFromElements().get(0);
        AST syntheticDotNode = generateSyntheticDotNodeForNonQualifiedPropertyRef(property, fromElement);
        return lookupProperty(syntheticDotNode, false, getCurrentClauseType() == HqlSqlTokenTypes.SELECT);
    }

    private AST generateSyntheticDotNodeForNonQualifiedPropertyRef(AST property, FromElement fromElement) {
        AST dot = getASTFactory().create(DOT, "{non-qualified-property-ref}");
        // TODO : better way?!?
        ((DotNode) dot).setPropertyPath(((FromReferenceNode) property).getPath());

        IdentNode syntheticAlias = (IdentNode) getASTFactory().create(IDENT, "{synthetic-alias}");
        syntheticAlias.setFromElement(fromElement);
        syntheticAlias.setResolved();

        dot.setFirstChild(syntheticAlias);
        dot.addChild(property);

        return dot;
    }

    @Override
    protected void processQuery(AST select, AST query) throws SemanticException {
        if (LOG.isDebugEnabled()) {
            LOG.debugf("processQuery() : %s", query.toStringTree());
        }

        try {
            QueryNode qn = (QueryNode) query;

            // Was there an explicit select expression?
            boolean explicitSelect = select != null && select.getNumberOfChildren() > 0;

            // Add in the EntityGraph attribute nodes.
            if (queryTranslatorImpl.getEntityGraphQueryHint() != null) {
                final boolean oldInEntityGraph = inEntityGraph;
                try {
                    inEntityGraph = true;
                    qn.getFromClause().getFromElements().addAll(
                            queryTranslatorImpl.getEntityGraphQueryHint().toFromElements(qn.getFromClause(), this));
                } finally {
                    inEntityGraph = oldInEntityGraph;
                }
            }

            if (!explicitSelect) {
                // No explicit select expression; render the id and properties
                // projection lists for every persister in the from clause into
                // a single 'token node'.
                //TODO: the only reason we need this stuff now is collection filters,
                //      we should get rid of derived select clause completely!
                createSelectClauseFromFromClause(qn);
            } else {
                // Use the explicitly declared select expression; determine the
                // return types indicated by each select token
                useSelectClause(select);
            }

            // After that, process the JOINs.
            // Invoke a delegate to do the work, as this is farily complex.
            JoinProcessor joinProcessor = new JoinProcessor(this);
            joinProcessor.processJoins(qn);

            // Attach any mapping-defined "ORDER BY" fragments
            Iterator itr = qn.getFromClause().getProjectionList().iterator();
            while (itr.hasNext()) {
                final FromElement fromElement = (FromElement) itr.next();
                //         if ( fromElement.isFetch() && fromElement.isCollectionJoin() ) {
                if (fromElement.isFetch() && fromElement.getQueryableCollection() != null) {
                    // Does the collection referenced by this FromElement
                    // specify an order-by attribute?  If so, attach it to
                    // the query's order-by
                    if (fromElement.getQueryableCollection().hasOrdering()) {
                        String orderByFragment = fromElement.getQueryableCollection()
                                .getSQLOrderByString(fromElement.getCollectionTableAlias());
                        qn.getOrderByClause().addOrderFragment(orderByFragment);
                    }
                    if (fromElement.getQueryableCollection().hasManyToManyOrdering()) {
                        String orderByFragment = fromElement.getQueryableCollection()
                                .getManyToManyOrderByString(fromElement.getTableAlias());
                        qn.getOrderByClause().addOrderFragment(orderByFragment);
                    }
                }
            }
        } finally {
            popFromClause();
        }
    }

    protected void postProcessDML(RestrictableStatement statement) throws SemanticException {
        statement.getFromClause().resolve();

        FromElement fromElement = (FromElement) statement.getFromClause().getFromElements().get(0);
        Queryable persister = fromElement.getQueryable();
        // Make #@%$^#^&# sure no alias is applied to the table name
        fromElement.setText(persister.getTableName());

        //      // append any filter fragments; the EMPTY_MAP is used under the assumption that
        //      // currently enabled filters should not affect this process
        //      if ( persister.getDiscriminatorType() != null ) {
        //         new SyntheticAndFactory( getASTFactory() ).addDiscriminatorWhereFragment(
        //                 statement,
        //                 persister,
        //                 java.util.Collections.EMPTY_MAP,
        //                 fromElement.getTableAlias()
        //         );
        //      }
        if (persister.getDiscriminatorType() != null || !queryTranslatorImpl.getEnabledFilters().isEmpty()) {
            new SyntheticAndFactory(this).addDiscriminatorWhereFragment(statement, persister,
                    queryTranslatorImpl.getEnabledFilters(), fromElement.getTableAlias());
        }

    }

    @Override
    protected void postProcessUpdate(AST update) throws SemanticException {
        UpdateStatement updateStatement = (UpdateStatement) update;

        postProcessDML(updateStatement);
    }

    @Override
    protected void postProcessDelete(AST delete) throws SemanticException {
        postProcessDML((DeleteStatement) delete);
    }

    @Override
    protected void postProcessInsert(AST insert) throws SemanticException, QueryException {
        InsertStatement insertStatement = (InsertStatement) insert;
        insertStatement.validate();

        SelectClause selectClause = insertStatement.getSelectClause();
        Queryable persister = insertStatement.getIntoClause().getQueryable();

        if (!insertStatement.getIntoClause().isExplicitIdInsertion()) {
            // the insert did not explicitly reference the id.  See if
            //      1) that is allowed
            //      2) whether we need to alter the SQL tree to account for id
            final IdentifierGenerator generator = persister.getIdentifierGenerator();
            if (!BulkInsertionCapableIdentifierGenerator.class.isInstance(generator)) {
                throw new QueryException(
                        "Invalid identifier generator encountered for implicit id handling as part of bulk insertions");
            }
            final BulkInsertionCapableIdentifierGenerator capableGenerator = BulkInsertionCapableIdentifierGenerator.class
                    .cast(generator);
            if (!capableGenerator.supportsBulkInsertionIdentifierGeneration()) {
                throw new QueryException(
                        "Identifier generator reported it does not support implicit id handling as part of bulk insertions");
            }

            final String fragment = capableGenerator.determineBulkInsertionIdentifierGenerationSelectFragment(
                    sessionFactoryHelper.getFactory().getDialect());
            if (fragment != null) {
                // we got a fragment from the generator, so alter the sql tree...
                //
                // first, wrap the fragment as a node
                AST fragmentNode = getASTFactory().create(HqlSqlTokenTypes.SQL_TOKEN, fragment);
                // next, rearrange the SQL tree to add the fragment node as the first select expression
                AST originalFirstSelectExprNode = selectClause.getFirstChild();
                selectClause.setFirstChild(fragmentNode);
                fragmentNode.setNextSibling(originalFirstSelectExprNode);
                // finally, prepend the id column name(s) to the insert-spec
                insertStatement.getIntoClause().prependIdColumnSpec();
            }
        }

        if (sessionFactoryHelper.getFactory().getDialect().supportsParametersInInsertSelect()) {
            AST child = selectClause.getFirstChild();
            int i = 0;
            while (child != null) {
                if (child instanceof ParameterNode) {
                    // infer the parameter type from the type listed in the INSERT INTO clause
                    ((ParameterNode) child).setExpectedType(insertStatement.getIntoClause()
                            .getInsertionTypes()[selectClause.getParameterPositions().get(i)]);
                    i++;
                }
                child = child.getNextSibling();
            }
        }

        final boolean includeVersionProperty = persister.isVersioned()
                && !insertStatement.getIntoClause().isExplicitVersionInsertion()
                && persister.isVersionPropertyInsertable();
        if (includeVersionProperty) {
            // We need to seed the version value as part of this bulk insert
            VersionType versionType = persister.getVersionType();
            AST versionValueNode = null;

            if (sessionFactoryHelper.getFactory().getDialect().supportsParametersInInsertSelect()) {
                int[] sqlTypes = versionType.sqlTypes(sessionFactoryHelper.getFactory());
                if (sqlTypes == null || sqlTypes.length == 0) {
                    throw new IllegalStateException(
                            versionType.getClass() + ".sqlTypes() returns null or empty array");
                }
                if (sqlTypes.length > 1) {
                    throw new IllegalStateException(versionType.getClass()
                            + ".sqlTypes() returns > 1 element; only single-valued versions are allowed.");
                }
                versionValueNode = getASTFactory().create(HqlSqlTokenTypes.PARAM, "?");
                ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification(versionType);
                ((ParameterNode) versionValueNode).setHqlParameterSpecification(paramSpec);
                parameterSpecs.add(0, paramSpec);

                if (sessionFactoryHelper.getFactory().getDialect().requiresCastingOfParametersInSelectClause()) {
                    // we need to wrtap the param in a cast()
                    MethodNode versionMethodNode = (MethodNode) getASTFactory().create(HqlSqlTokenTypes.METHOD_CALL,
                            "(");
                    AST methodIdentNode = getASTFactory().create(HqlSqlTokenTypes.IDENT, "cast");
                    versionMethodNode.addChild(methodIdentNode);
                    versionMethodNode.initializeMethodNode(methodIdentNode, true);
                    AST castExprListNode = getASTFactory().create(HqlSqlTokenTypes.EXPR_LIST, "exprList");
                    methodIdentNode.setNextSibling(castExprListNode);
                    castExprListNode.addChild(versionValueNode);
                    versionValueNode.setNextSibling(getASTFactory().create(HqlSqlTokenTypes.IDENT,
                            sessionFactoryHelper.getFactory().getDialect().getTypeName(sqlTypes[0])));
                    processFunction(versionMethodNode, true);
                    versionValueNode = versionMethodNode;
                }
            } else {
                if (isIntegral(versionType)) {
                    try {
                        Object seedValue = versionType.seed(null);
                        versionValueNode = getASTFactory().create(HqlSqlTokenTypes.SQL_TOKEN, seedValue.toString());
                    } catch (Throwable t) {
                        throw new QueryException(
                                "could not determine seed value for version on bulk insert [" + versionType + "]");
                    }
                } else if (isDatabaseGeneratedTimestamp(versionType)) {
                    String functionName = sessionFactoryHelper.getFactory().getDialect()
                            .getCurrentTimestampSQLFunctionName();
                    versionValueNode = getASTFactory().create(HqlSqlTokenTypes.SQL_TOKEN, functionName);
                } else {
                    throw new QueryException("cannot handle version type [" + versionType
                            + "] on bulk inserts with dialects not supporting parameterSpecs in insert-select statements");
                }
            }

            AST currentFirstSelectExprNode = selectClause.getFirstChild();
            selectClause.setFirstChild(versionValueNode);
            versionValueNode.setNextSibling(currentFirstSelectExprNode);

            insertStatement.getIntoClause().prependVersionColumnSpec();
        }

        if (insertStatement.getIntoClause().isDiscriminated()) {
            String sqlValue = insertStatement.getIntoClause().getQueryable().getDiscriminatorSQLValue();
            AST discrimValue = getASTFactory().create(HqlSqlTokenTypes.SQL_TOKEN, sqlValue);
            insertStatement.getSelectClause().addChild(discrimValue);
        }

    }

    private boolean isDatabaseGeneratedTimestamp(Type type) {
        // currently only the Hibernate-supplied DbTimestampType is supported here
        return DbTimestampType.class.isAssignableFrom(type.getClass());
    }

    private boolean isIntegral(Type type) {
        return Long.class.isAssignableFrom(type.getReturnedClass())
                || Integer.class.isAssignableFrom(type.getReturnedClass())
                || long.class.isAssignableFrom(type.getReturnedClass())
                || int.class.isAssignableFrom(type.getReturnedClass());
    }

    private void useSelectClause(AST select) throws SemanticException {
        selectClause = (SelectClause) select;
        selectClause.initializeExplicitSelectClause(currentFromClause);
    }

    private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticException {
        AST select = astFactory.create(SELECT_CLAUSE, "{derived select clause}");
        AST sibling = qn.getFromClause();
        qn.setFirstChild(select);
        select.setNextSibling(sibling);
        selectClause = (SelectClause) select;
        selectClause.initializeDerivedSelectClause(currentFromClause);
        LOG.debug("Derived SELECT clause created.");
    }

    @Override
    protected void resolve(AST node) throws SemanticException {
        resolve(node, null);
    }

    @Override
    protected void resolve(AST node, AST predicateNode) throws SemanticException {
        if (node != null) {
            // This is called when it's time to fully resolve a path expression.
            ResolvableNode r = (ResolvableNode) node;
            if (isInFunctionCall()) {
                r.resolveInFunctionCall(false, true);
            } else {
                r.resolve(false, true, null, null, predicateNode); // Generate implicit joins, only if necessary.
            }
        }
    }

    @Override
    protected void resolveSelectExpression(AST node) throws SemanticException {
        // This is called when it's time to fully resolve a path expression.
        int type = node.getType();
        switch (type) {
        case DOT: {
            DotNode dot = (DotNode) node;
            dot.resolveSelectExpression();
            break;
        }
        case ALIAS_REF: {
            // Notify the FROM element that it is being referenced by the select.
            FromReferenceNode aliasRefNode = (FromReferenceNode) node;
            //aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here?
            aliasRefNode.resolve(false, false); //TODO: is it kosher to do it here?
            FromElement fromElement = aliasRefNode.getFromElement();
            if (fromElement != null) {
                fromElement.setIncludeSubclasses(true);
            }
            break;
        }
        default: {
            break;
        }
        }
    }

    @Override
    protected void beforeSelectClause() throws SemanticException {
        // Turn off includeSubclasses on all FromElements.
        FromClause from = getCurrentFromClause();
        List fromElements = from.getFromElements();
        for (Iterator iterator = fromElements.iterator(); iterator.hasNext();) {
            FromElement fromElement = (FromElement) iterator.next();
            fromElement.setIncludeSubclasses(false);
        }
    }

    @Override
    protected AST generatePositionalParameter(AST delimiterNode, AST numberNode) throws SemanticException {
        // todo : we check this multiple times
        if (getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() && namedParameters != null) {
            throw new SemanticException(
                    "Cannot mix positional and named parameters: " + queryTranslatorImpl.getQueryString());
        }

        if (numberNode == null) {
            throw new QueryException(String.format(Locale.ROOT, ERROR_LEGACY_ORDINAL_PARAMS_NO_LONGER_SUPPORTED,
                    queryTranslatorImpl.getQueryString()));
        }
        final String positionString = numberNode.getText();
        final int label = Integer.parseInt(positionString);
        trackPositionalParameterPositions(label);

        final ParameterNode parameter = (ParameterNode) astFactory.create(PARAM, positionString);
        parameter.setText("?");

        final int queryParamtersPosition = isFilter() ? label : label - 1;
        final PositionalParameterSpecification paramSpec = new PositionalParameterSpecification(
                delimiterNode.getLine(), delimiterNode.getColumn(), label, queryParamtersPosition);
        parameter.setHqlParameterSpecification(paramSpec);
        parameterSpecs.add(paramSpec);

        return parameter;
    }

    @SuppressWarnings("unchecked")
    private void trackPositionalParameterPositions(int label) {
        if (positionalParameters == null) {
            positionalParameters = new HashMap();
        }

        final Integer loc = parameterCount++;

        final Object existingValue = positionalParameters.get(label);
        if (existingValue == null) {
            positionalParameters.put(label, loc);
        } else if (existingValue instanceof Integer) {
            final ArrayList list = new ArrayList();
            positionalParameters.put(label, list);
            list.add(existingValue);
            list.add(loc);
        } else {
            ((List) existingValue).add(loc);
        }
    }

    @Override
    protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {
        if (getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() && positionalParameters != null) {
            throw new SemanticException(
                    "Cannot mix positional and named parameters: " + queryTranslatorImpl.getQueryString());
        }
        final String name = nameNode.getText();
        trackNamedParameterPositions(name);

        // create the node initially with the param name so that it shows
        // appropriately in the "original text" attribute
        final ParameterNode parameter = (ParameterNode) astFactory.create(NAMED_PARAM, name);
        parameter.setText("?");

        final NamedParameterSpecification paramSpec = new NamedParameterSpecification(delimiterNode.getLine(),
                delimiterNode.getColumn(), name);
        parameter.setHqlParameterSpecification(paramSpec);
        parameterSpecs.add(paramSpec);

        return parameter;
    }

    @SuppressWarnings("unchecked")
    private void trackNamedParameterPositions(String name) {
        if (namedParameters == null) {
            namedParameters = new HashMap();
        }

        final Integer loc = parameterCount++;

        final Object existingValue = namedParameters.get(name);
        if (existingValue == null) {
            namedParameters.put(name, loc);
        } else if (existingValue instanceof Integer) {
            ArrayList<Integer> list = new ArrayList<>(4);
            list.add((Integer) existingValue);
            list.add(loc);
            namedParameters.put(name, list);
        } else {
            ((List) existingValue).add(loc);
        }
    }

    @Override
    protected void processConstant(AST constant) throws SemanticException {
        literalProcessor.processConstant(constant, true); // Use the delegate, resolve identifiers as FROM element aliases.
    }

    @Override
    protected void processBoolean(AST constant) throws SemanticException {
        literalProcessor.processBoolean(constant); // Use the delegate.
    }

    @Override
    protected void processNumericLiteral(AST literal) {
        literalProcessor.processNumeric(literal);
    }

    @Override
    protected void processIndex(AST indexOp) throws SemanticException {
        IndexNode indexNode = (IndexNode) indexOp;
        indexNode.resolve(true, true);
    }

    @Override
    protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
        MethodNode methodNode = (MethodNode) functionCall;
        methodNode.resolve(inSelect);
    }

    @Override
    protected void processCastFunction(AST castFunctionCall, boolean inSelect) throws SemanticException {
        CastFunctionNode castFunctionNode = (CastFunctionNode) castFunctionCall;
        castFunctionNode.resolve(inSelect);
    }

    @Override
    protected void processAggregation(AST node, boolean inSelect) throws SemanticException {
        AggregateNode aggregateNode = (AggregateNode) node;
        aggregateNode.resolve();
    }

    @Override
    protected void processConstructor(AST constructor) throws SemanticException {
        ConstructorNode constructorNode = (ConstructorNode) constructor;
        constructorNode.prepare();
    }

    @Override
    protected void setAlias(AST selectExpr, AST ident) {
        ((SelectExpression) selectExpr).setAlias(ident.getText());
        // only put the alias (i.e., result variable) in selectExpressionsByResultVariable
        // if is not defined in a subquery.
        if (!isSubQuery()) {
            selectExpressionsByResultVariable.put(ident.getText(), (SelectExpression) selectExpr);
        }
    }

    @Override
    protected boolean isOrderExpressionResultVariableRef(AST orderExpressionNode) throws SemanticException {
        // ORDER BY is not supported in a subquery
        // TODO: should an exception be thrown if an ORDER BY is in a subquery?
        if (!isSubQuery() && orderExpressionNode.getType() == IDENT
                && selectExpressionsByResultVariable.containsKey(orderExpressionNode.getText())) {
            return true;
        }
        return false;
    }

    @Override
    protected boolean isGroupExpressionResultVariableRef(AST groupExpressionNode) throws SemanticException {
        // Aliases are not sensible in subqueries
        if (getDialect().supportsSelectAliasInGroupByClause() && !isSubQuery()
                && groupExpressionNode.getType() == IDENT
                && selectExpressionsByResultVariable.containsKey(groupExpressionNode.getText())) {
            return true;
        }
        return false;
    }

    @Override
    protected void handleResultVariableRef(AST resultVariableRef) throws SemanticException {
        if (isSubQuery()) {
            throw new SemanticException("References to result variables in subqueries are not supported.");
        }
        ((ResultVariableRefNode) resultVariableRef)
                .setSelectExpression(selectExpressionsByResultVariable.get(resultVariableRef.getText()));
    }

    /**
     * Returns the locations of all occurrences of the named parameter.
     */
    public int[] getNamedParameterLocations(String name) throws QueryException {
        Object o = namedParameters.get(name);
        if (o == null) {
            throw new QueryException(QueryTranslator.ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR + name,
                    queryTranslatorImpl.getQueryString());
        }
        if (o instanceof Integer) {
            return new int[] { (Integer) o };
        } else {
            return ArrayHelper.toIntArray((ArrayList) o);
        }
    }

    public void addQuerySpaces(Serializable[] spaces) {
        querySpaces.addAll(Arrays.asList(spaces));
    }

    public Type[] getReturnTypes() {
        return selectClause.getQueryReturnTypes();
    }

    public String[] getReturnAliases() {
        return selectClause.getQueryReturnAliases();
    }

    public SelectClause getSelectClause() {
        return selectClause;
    }

    public FromClause getFinalFromClause() {
        FromClause top = currentFromClause;
        while (top.getParentFromClause() != null) {
            top = top.getParentFromClause();
        }
        return top;
    }

    public boolean isShallowQuery() {
        // select clauses for insert statements should alwasy be treated as shallow
        return getStatementType() == INSERT || queryTranslatorImpl.isShallowQuery();
    }

    public Map getEnabledFilters() {
        return queryTranslatorImpl.getEnabledFilters();
    }

    public LiteralProcessor getLiteralProcessor() {
        return literalProcessor;
    }

    public ASTPrinter getASTPrinter() {
        return TokenPrinters.SQL_TOKEN_PRINTER;
    }

    public ArrayList<ParameterSpecification> getParameterSpecs() {
        return parameterSpecs;
    }

    public int getNumberOfParametersInSetClause() {
        return numberOfParametersInSetClause;
    }

    @Override
    protected void evaluateAssignment(AST eq) throws SemanticException {
        prepareLogicOperator(eq);
        Queryable persister = getCurrentFromClause().getFromElement().getQueryable();
        evaluateAssignment(eq, persister, -1);
    }

    private void evaluateAssignment(AST eq, Queryable persister, int targetIndex) {
        if (persister.isMultiTable()) {
            // no need to even collect this information if the persister is considered multi-table
            AssignmentSpecification specification = new AssignmentSpecification(eq, persister);
            if (targetIndex >= 0) {
                assignmentSpecifications.add(targetIndex, specification);
            } else {
                assignmentSpecifications.add(specification);
            }
            numberOfParametersInSetClause += specification.getParameters().length;
        }
    }

    public ArrayList getAssignmentSpecifications() {
        return assignmentSpecifications;
    }

    @Override
    protected AST createIntoClause(String path, AST propertySpec) throws SemanticException {
        Queryable persister = (Queryable) getSessionFactoryHelper().requireClassPersister(path);

        IntoClause intoClause = (IntoClause) getASTFactory().create(INTO, persister.getEntityName());
        intoClause.setFirstChild(propertySpec);
        intoClause.initialize(persister);

        addQuerySpaces(persister.getQuerySpaces());

        return intoClause;
    }

    @Override
    protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticException {
        UpdateStatement updateStatement = (UpdateStatement) updateNode;
        FromClause fromClause = updateStatement.getFromClause();
        if (versioned != null) {
            // Make sure that the persister is versioned
            Queryable persister = fromClause.getFromElement().getQueryable();
            if (!persister.isVersioned()) {
                throw new SemanticException("increment option specified for update of non-versioned entity");
            }

            VersionType versionType = persister.getVersionType();
            if (versionType instanceof UserVersionType) {
                throw new SemanticException("user-defined version types not supported for increment option");
            }

            AST eq = getASTFactory().create(HqlSqlTokenTypes.EQ, "=");
            AST versionPropertyNode = generateVersionPropertyNode(persister);

            eq.setFirstChild(versionPropertyNode);

            AST versionIncrementNode = null;
            if (isTimestampBasedVersion(versionType)) {
                versionIncrementNode = getASTFactory().create(HqlSqlTokenTypes.PARAM, "?");
                ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification(versionType);
                ((ParameterNode) versionIncrementNode).setHqlParameterSpecification(paramSpec);
                parameterSpecs.add(0, paramSpec);
            } else {
                // Not possible to simply re-use the versionPropertyNode here as it causes
                // OOM errors due to circularity :(
                versionIncrementNode = getASTFactory().create(HqlSqlTokenTypes.PLUS, "+");
                versionIncrementNode.setFirstChild(generateVersionPropertyNode(persister));
                versionIncrementNode.addChild(getASTFactory().create(HqlSqlTokenTypes.IDENT, "1"));
            }

            eq.addChild(versionIncrementNode);

            evaluateAssignment(eq, persister, 0);

            AST setClause = updateStatement.getSetClause();
            AST currentFirstSetElement = setClause.getFirstChild();
            setClause.setFirstChild(eq);
            eq.setNextSibling(currentFirstSetElement);
        }
    }

    private boolean isTimestampBasedVersion(VersionType versionType) {
        final Class javaType = versionType.getReturnedClass();
        return Date.class.isAssignableFrom(javaType) || Calendar.class.isAssignableFrom(javaType);
    }

    private AST generateVersionPropertyNode(Queryable persister) throws SemanticException {
        String versionPropertyName = persister.getPropertyNames()[persister.getVersionProperty()];
        AST versionPropertyRef = getASTFactory().create(HqlSqlTokenTypes.IDENT, versionPropertyName);
        AST versionPropertyNode = lookupNonQualifiedProperty(versionPropertyRef);
        resolve(versionPropertyNode);
        return versionPropertyNode;
    }

    @Override
    protected void prepareLogicOperator(AST operator) throws SemanticException {
        ((OperatorNode) operator).initialize();
    }

    @Override
    protected void prepareArithmeticOperator(AST operator) throws SemanticException {
        ((OperatorNode) operator).initialize();
    }

    @Override
    protected void validateMapPropertyExpression(AST node) throws SemanticException {
        try {
            FromReferenceNode fromReferenceNode = (FromReferenceNode) node;
            QueryableCollection collectionPersister = fromReferenceNode.getFromElement().getQueryableCollection();
            if (!Map.class.isAssignableFrom(collectionPersister.getCollectionType().getReturnedClass())) {
                throw new SemanticException("node did not reference a map");
            }
        } catch (SemanticException se) {
            throw se;
        } catch (Throwable t) {
            throw new SemanticException("node did not reference a map");
        }
    }

    public Set<String> getTreatAsDeclarationsByPath(String path) {
        return hqlParser.getTreatMap().get(path);
    }

    public Dialect getDialect() {
        return sessionFactoryHelper.getFactory().getServiceRegistry().getService(JdbcServices.class).getDialect();
    }

    public static void panic() {
        throw new QueryException("TreeWalker: panic");
    }
}