1   /*
2    * Hibernate, Relational Persistence for Idiomatic Java
3    *
4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5    * indicated by the @author tags or express copyright attribution
6    * statements applied by the authors.  All third-party contributions are
7    * distributed under license by Red Hat Middleware LLC.
8    *
9    * This copyrighted material is made available to anyone wishing to use, modify,
10   * copy, or redistribute it subject to the terms and conditions of the GNU
11   * Lesser General Public License, as published by the Free Software Foundation.
12   *
13   * This program is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15   * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
16   * for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public License
19   * along with this distribution; if not, write to:
20   * Free Software Foundation, Inc.
21   * 51 Franklin Street, Fifth Floor
22   * Boston, MA  02110-1301  USA
23   *
24   */
25  package org.hibernate.hql.internal.ast;
26  
27  import java.io.Serializable;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Calendar;
31  import java.util.Date;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.hibernate.HibernateException;
40  import org.hibernate.QueryException;
41  import org.hibernate.engine.internal.JoinSequence;
42  import org.hibernate.engine.internal.ParameterBinder;
43  import org.hibernate.engine.spi.SessionFactoryImplementor;
44  import org.hibernate.hql.internal.antlr.HqlSqlBaseWalker;
45  import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
46  import org.hibernate.hql.internal.antlr.HqlTokenTypes;
47  import org.hibernate.hql.internal.antlr.SqlTokenTypes;
48  import org.hibernate.hql.internal.ast.tree.AggregateNode;
49  import org.hibernate.hql.internal.ast.tree.AssignmentSpecification;
50  import org.hibernate.hql.internal.ast.tree.CastFunctionNode;
51  import org.hibernate.hql.internal.ast.tree.CollectionFunction;
52  import org.hibernate.hql.internal.ast.tree.ConstructorNode;
53  import org.hibernate.hql.internal.ast.tree.DeleteStatement;
54  import org.hibernate.hql.internal.ast.tree.DotNode;
55  import org.hibernate.hql.internal.ast.tree.FromClause;
56  import org.hibernate.hql.internal.ast.tree.FromElement;
57  import org.hibernate.hql.internal.ast.tree.FromElementFactory;
58  import org.hibernate.hql.internal.ast.tree.FromReferenceNode;
59  import org.hibernate.hql.internal.ast.tree.IdentNode;
60  import org.hibernate.hql.internal.ast.tree.IndexNode;
61  import org.hibernate.hql.internal.ast.tree.InsertStatement;
62  import org.hibernate.hql.internal.ast.tree.IntoClause;
63  import org.hibernate.hql.internal.ast.tree.MethodNode;
64  import org.hibernate.hql.internal.ast.tree.OperatorNode;
65  import org.hibernate.hql.internal.ast.tree.ParameterContainer;
66  import org.hibernate.hql.internal.ast.tree.ParameterNode;
67  import org.hibernate.hql.internal.ast.tree.QueryNode;
68  import org.hibernate.hql.internal.ast.tree.ResolvableNode;
69  import org.hibernate.hql.internal.ast.tree.RestrictableStatement;
70  import org.hibernate.hql.internal.ast.tree.ResultVariableRefNode;
71  import org.hibernate.hql.internal.ast.tree.SelectClause;
72  import org.hibernate.hql.internal.ast.tree.SelectExpression;
73  import org.hibernate.hql.internal.ast.tree.UpdateStatement;
74  import org.hibernate.hql.internal.ast.util.ASTPrinter;
75  import org.hibernate.hql.internal.ast.util.ASTUtil;
76  import org.hibernate.hql.internal.ast.util.AliasGenerator;
77  import org.hibernate.hql.internal.ast.util.JoinProcessor;
78  import org.hibernate.hql.internal.ast.util.LiteralProcessor;
79  import org.hibernate.hql.internal.ast.util.NodeTraverser;
80  import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
81  import org.hibernate.hql.internal.ast.util.SyntheticAndFactory;
82  import org.hibernate.hql.spi.QueryTranslator;
83  import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
84  import org.hibernate.id.IdentifierGenerator;
85  import org.hibernate.internal.CoreMessageLogger;
86  import org.hibernate.internal.util.StringHelper;
87  import org.hibernate.internal.util.collections.ArrayHelper;
88  import org.hibernate.param.CollectionFilterKeyParameterSpecification;
89  import org.hibernate.param.NamedParameterSpecification;
90  import org.hibernate.param.ParameterSpecification;
91  import org.hibernate.param.PositionalParameterSpecification;
92  import org.hibernate.param.VersionTypeSeedParameterSpecification;
93  import org.hibernate.persister.collection.QueryableCollection;
94  import org.hibernate.persister.entity.Queryable;
95  import org.hibernate.sql.JoinType;
96  import org.hibernate.type.AssociationType;
97  import org.hibernate.type.ComponentType;
98  import org.hibernate.type.DbTimestampType;
99  import org.hibernate.type.Type;
100 import org.hibernate.type.VersionType;
101 import org.hibernate.usertype.UserVersionType;
102 import org.jboss.logging.Logger;
103 
104 import antlr.ASTFactory;
105 import antlr.RecognitionException;
106 import antlr.SemanticException;
107 import antlr.collections.AST;
108 
109 /**
110  * Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase).
111  * <ul>
112  * <li>Isolates the Hibernate API-specific code from the ANTLR generated code.</li>
113  * <li>Handles the SQL fragments generated by the persisters in order to create the SELECT and FROM clauses,
114  * taking into account the joins and projections that are implied by the mappings (persister/queryable).</li>
115  * <li>Uses SqlASTFactory to create customized AST nodes.</li>
116  * </ul>
117  *
118  * @see SqlASTFactory
119  */
120 public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, ParameterBinder.NamedParameterSource {
121 
122     private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, HqlSqlWalker.class.getName());
123 
124 	private final QueryTranslatorImpl queryTranslatorImpl;
125 	private final HqlParser hqlParser;
126 	private final SessionFactoryHelper sessionFactoryHelper;
127 	private final Map tokenReplacements;
128 	private final AliasGenerator aliasGenerator = new AliasGenerator();
129 	private final LiteralProcessor literalProcessor;
130 	private final ParseErrorHandler parseErrorHandler;
131 	private final ASTPrinter printer;
132 	private final String collectionFilterRole;
133 
134 	private FromClause currentFromClause = null;
135 	private SelectClause selectClause;
136 
137 	/**
138 	 * Maps each top-level result variable to its SelectExpression;
139 	 * (excludes result variables defined in subqueries)
140 	 **/
141 	private Map<String, SelectExpression> selectExpressionsByResultVariable = new HashMap();
142 
143 	private Set querySpaces = new HashSet();
144 
145 	private int parameterCount;
146 	private Map namedParameters = new HashMap();
147 	private ArrayList parameters = new ArrayList();
148 	private int numberOfParametersInSetClause;
149 	private int positionalParameterCount;
150 
151 	private ArrayList assignmentSpecifications = new ArrayList();
152 
153 	private JoinType impliedJoinType = JoinType.INNER_JOIN;
154 
155 	/**
156 	 * Create a new tree transformer.
157 	 *
158 	 * @param qti Back pointer to the query translator implementation that is using this tree transform.
159 	 * @param sfi The session factory implementor where the Hibernate mappings can be found.
160 	 * @param parser A reference to the phase-1 parser
161 	 * @param tokenReplacements Registers the token replacement map with the walker.  This map will
162 	 * be used to substitute function names and constants.
163 	 * @param collectionRole The collection role name of the collection used as the basis for the
164 	 * filter, NULL if this is not a collection filter compilation.
165 	 */
166 	public HqlSqlWalker(
167 			QueryTranslatorImpl qti,
168 			SessionFactoryImplementor sfi,
169 			HqlParser parser,
170 			Map tokenReplacements,
171 			String collectionRole) {
172 		setASTFactory( new SqlASTFactory( this ) );
173 		// Initialize the error handling delegate.
174 		this.parseErrorHandler = new ErrorCounter();
175 		this.queryTranslatorImpl = qti;
176 		this.sessionFactoryHelper = new SessionFactoryHelper( sfi );
177 		this.literalProcessor = new LiteralProcessor( this );
178 		this.tokenReplacements = tokenReplacements;
179 		this.collectionFilterRole = collectionRole;
180 		this.hqlParser = parser;
181 		this.printer = new ASTPrinter( SqlTokenTypes.class );
182 	}
183 
184 
185 	// handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
186 
187 	private int traceDepth = 0;
188 
189 	@Override
190 	public void traceIn(String ruleName, AST tree) {
191 		if ( !LOG.isTraceEnabled() ) return;
192 		if ( inputState.guessing > 0 ) return;
193 		String prefix = StringHelper.repeat( '-', ( traceDepth++ * 2 ) ) + "-> ";
194 		String traceText = ruleName + " (" + buildTraceNodeName( tree ) + ")";
195 		LOG.trace( prefix + traceText );
196 	}
197 
198 	private String buildTraceNodeName(AST tree) {
199 		return tree == null
200 				? "???"
201 				: tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]";
202 	}
203 
204 	@Override
205 	public void traceOut(String ruleName, AST tree) {
206 		if ( !LOG.isTraceEnabled() ) return;
207 		if ( inputState.guessing > 0 ) return;
208 		String prefix = "<-" + StringHelper.repeat( '-', ( --traceDepth * 2 ) ) + " ";
209 		LOG.trace( prefix + ruleName );
210 	}
211 
212 	@Override
213     protected void prepareFromClauseInputTree(AST fromClauseInput) {
214 		if ( !isSubQuery() ) {
215 //			// inject param specifications to account for dynamic filter param values
216 //			if ( ! getEnabledFilters().isEmpty() ) {
217 //				Iterator filterItr = getEnabledFilters().values().iterator();
218 //				while ( filterItr.hasNext() ) {
219 //					FilterImpl filter = ( FilterImpl ) filterItr.next();
220 //					if ( ! filter.getFilterDefinition().getParameterNames().isEmpty() ) {
221 //						Iterator paramItr = filter.getFilterDefinition().getParameterNames().iterator();
222 //						while ( paramItr.hasNext() ) {
223 //							String parameterName = ( String ) paramItr.next();
224 //							// currently param filters *only* work with single-column parameter types;
225 //							// if that limitation is ever lifted, this logic will need to change to account for that
226 //							ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
227 //							DynamicFilterParameterSpecification paramSpec = new DynamicFilterParameterSpecification(
228 //									filter.getName(),
229 //									parameterName,
230 //									filter.getFilterDefinition().getParameterType( parameterName ),
231 //									 positionalParameterCount++
232 //							);
233 //							collectionFilterKeyParameter.setHqlParameterSpecification( paramSpec );
234 //							parameters.add( paramSpec );
235 //						}
236 //					}
237 //				}
238 //			}
239 
240 			if ( isFilter() ) {
241                 // Handle collection-filter compilation.
242 				// IMPORTANT NOTE: This is modifying the INPUT (HQL) tree, not the output tree!
243 				QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole );
244 				Type collectionElementType = persister.getElementType();
245 				if ( !collectionElementType.isEntityType() ) {
246 					throw new QueryException( "collection of values in filter: this" );
247 				}
248 
249 				String collectionElementEntityName = persister.getElementPersister().getEntityName();
250 				ASTFactory inputAstFactory = hqlParser.getASTFactory();
251 				AST fromElement = ASTUtil.create( inputAstFactory, HqlTokenTypes.FILTER_ENTITY, collectionElementEntityName );
252 				ASTUtil.createSibling( inputAstFactory, HqlTokenTypes.ALIAS, "this", fromElement );
253 				fromClauseInput.addChild( fromElement );
254 				// Show the modified AST.
255                 LOG.debug("prepareFromClauseInputTree() : Filter - Added 'this' as a from element...");
256 				queryTranslatorImpl.showHqlAst( hqlParser.getAST() );
257 
258 				// Create a parameter specification for the collection filter...
259 				Type collectionFilterKeyType = sessionFactoryHelper.requireQueryableCollection( collectionFilterRole ).getKeyType();
260 				ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
261 				CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification(
262 						collectionFilterRole, collectionFilterKeyType, positionalParameterCount++
263 				);
264 				collectionFilterKeyParameter.setHqlParameterSpecification( collectionFilterKeyParameterSpec );
265 				parameters.add( collectionFilterKeyParameterSpec );
266 			}
267 		}
268 	}
269 
270 	public boolean isFilter() {
271 		return collectionFilterRole != null;
272 	}
273 
274 	public String getCollectionFilterRole() {
275 		return collectionFilterRole;
276 	}
277 
278 	public SessionFactoryHelper getSessionFactoryHelper() {
279 		return sessionFactoryHelper;
280 	}
281 
282 	public Map getTokenReplacements() {
283 		return tokenReplacements;
284 	}
285 
286 	public AliasGenerator getAliasGenerator() {
287 		return aliasGenerator;
288 	}
289 
290 	public FromClause getCurrentFromClause() {
291 		return currentFromClause;
292 	}
293 
294 	public ParseErrorHandler getParseErrorHandler() {
295 		return parseErrorHandler;
296 	}
297 
298 	@Override
299     public void reportError(RecognitionException e) {
300 		parseErrorHandler.reportError( e ); // Use the delegate.
301 	}
302 
303 	@Override
304     public void reportError(String s) {
305 		parseErrorHandler.reportError( s ); // Use the delegate.
306 	}
307 
308 	@Override
309     public void reportWarning(String s) {
310 		parseErrorHandler.reportWarning( s );
311 	}
312 
313 	/**
314 	 * Returns the set of unique query spaces (a.k.a.
315 	 * table names) that occurred in the query.
316 	 *
317 	 * @return A set of table names (Strings).
318 	 */
319 	public Set getQuerySpaces() {
320 		return querySpaces;
321 	}
322 
323 	@Override
324     protected AST createFromElement(String path, AST alias, AST propertyFetch) throws SemanticException {
325 		FromElement fromElement = currentFromClause.addFromElement( path, alias );
326 		fromElement.setAllPropertyFetch(propertyFetch!=null);
327 		return fromElement;
328 	}
329 
330 	@Override
331     protected AST createFromFilterElement(AST filterEntity, AST alias) throws SemanticException {
332 		FromElement fromElement = currentFromClause.addFromElement( filterEntity.getText(), alias );
333 		FromClause fromClause = fromElement.getFromClause();
334 		QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole );
335 		// Get the names of the columns used to link between the collection
336 		// owner and the collection elements.
337 		String[] keyColumnNames = persister.getKeyColumnNames();
338 		String fkTableAlias = persister.isOneToMany()
339 				? fromElement.getTableAlias()
340 				: fromClause.getAliasGenerator().createName( collectionFilterRole );
341 		JoinSequence join = sessionFactoryHelper.createJoinSequence();
342 		join.setRoot( persister, fkTableAlias );
343 		if ( !persister.isOneToMany() ) {
344 			join.addJoin( ( AssociationType ) persister.getElementType(),
345 					fromElement.getTableAlias(),
346 					JoinType.INNER_JOIN,
347 					persister.getElementColumnNames( fkTableAlias ) );
348 		}
349 		join.addCondition( fkTableAlias, keyColumnNames, " = ?" );
350 		fromElement.setJoinSequence( join );
351 		fromElement.setFilter( true );
352         LOG.debug("createFromFilterElement() : processed filter FROM element.");
353 		return fromElement;
354 	}
355 
356 	@Override
357     protected void createFromJoinElement(
358 	        AST path,
359 	        AST alias,
360 	        int joinType,
361 	        AST fetchNode,
362 	        AST propertyFetch,
363 	        AST with) throws SemanticException {
364 		boolean fetch = fetchNode != null;
365 		if ( fetch && isSubQuery() ) {
366 			throw new QueryException( "fetch not allowed in subquery from-elements" );
367 		}
368 		// The path AST should be a DotNode, and it should have been evaluated already.
369 		if ( path.getType() != SqlTokenTypes.DOT ) {
370 			throw new SemanticException( "Path expected for join!" );
371 		}
372 		DotNode dot = ( DotNode ) path;
373 		JoinType hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType );
374 		dot.setJoinType( hibernateJoinType );	// Tell the dot node about the join type.
375 		dot.setFetch( fetch );
376 		// Generate an explicit join for the root dot node.   The implied joins will be collected and passed up
377 		// to the root dot node.
378 		dot.resolve( true, false, alias == null ? null : alias.getText() );
379 
380 		final FromElement fromElement;
381 		if ( dot.getDataType() != null && dot.getDataType().isComponentType() ) {
382 			FromElementFactory factory = new FromElementFactory(
383 					getCurrentFromClause(),
384 					dot.getLhs().getFromElement(),
385 					dot.getPropertyPath(),
386 					alias == null ? null : alias.getText(),
387 					null,
388 					false
389 			);
390 			fromElement = factory.createComponentJoin( (ComponentType) dot.getDataType() );
391 		}
392 		else {
393 			fromElement = dot.getImpliedJoin();
394 			fromElement.setAllPropertyFetch( propertyFetch != null );
395 
396 			if ( with != null ) {
397 				if ( fetch ) {
398 					throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
399 				}
400 				handleWithFragment( fromElement, with );
401 			}
402 		}
403 
404         if (LOG.isDebugEnabled()) LOG.debugf("createFromJoinElement() : %s",
405                                              getASTPrinter().showAsString(fromElement, "-- join tree --"));
406 	}
407 
408 	private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException {
409 		try {
410 			withClause( hqlWithNode );
411 			AST hqlSqlWithNode = returnAST;
412             if (LOG.isDebugEnabled()) LOG.debugf("handleWithFragment() : %s",
413                                                  getASTPrinter().showAsString(hqlSqlWithNode, "-- with clause --"));
414 			WithClauseVisitor visitor = new WithClauseVisitor( fromElement );
415 			NodeTraverser traverser = new NodeTraverser( visitor );
416 			traverser.traverseDepthFirst( hqlSqlWithNode );
417 
418 			String withClauseJoinAlias = visitor.getJoinAlias();
419 			if ( withClauseJoinAlias == null ) {
420 				withClauseJoinAlias = fromElement.getCollectionTableAlias();
421 			}
422 			else {
423 				FromElement referencedFromElement = visitor.getReferencedFromElement();
424 				if ( referencedFromElement != fromElement ) {
425 					LOG.warn( "with-clause expressions do not reference the from-clause element to which the with-clause was associated.  The query may not work as expected..."
426 							+ queryTranslatorImpl.getQueryString() );
427 				}
428 			}
429 
430 			SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() );
431 			sql.whereExpr( hqlSqlWithNode.getFirstChild() );
432 
433 			fromElement.setWithClauseFragment( withClauseJoinAlias, "(" + sql.getSQL() + ")" );
434 		}
435 		catch( SemanticException e ) {
436 			throw e;
437 		}
438 		catch( InvalidWithClauseException e ) {
439 			throw e;
440 		}
441 		catch ( Exception e) {
442 			throw new SemanticException( e.getMessage() );
443 		}
444 	}
445 
446 	private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy {
447 		private final FromElement joinFragment;
448 		private FromElement referencedFromElement;
449 		private String joinAlias;
450 
451 		public WithClauseVisitor(FromElement fromElement) {
452 			this.joinFragment = fromElement;
453 		}
454 
455 		public void visit(AST node) {
456             // TODO : currently expects that the individual with expressions apply to the same sql table join.
457 			//      This may not be the case for joined-subclass where the property values
458 			//      might be coming from different tables in the joined hierarchy.  At some
459 			//      point we should expand this to support that capability.  However, that has
460 			//      some difficulties:
461 			//          1) the biggest is how to handle ORs when the individual comparisons are
462 			//              linked to different sql joins.
463 			//          2) here we would need to track each comparison individually, along with
464 			//              the join alias to which it applies and then pass that information
465 			//              back to the FromElement so it can pass it along to the JoinSequence
466 			if ( node instanceof DotNode ) {
467 				DotNode dotNode = ( DotNode ) node;
468 				FromElement fromElement = dotNode.getFromElement();
469 				if ( referencedFromElement != null ) {
470 					if ( fromElement != referencedFromElement ) {
471 						throw new HibernateException( "with-clause referenced two different from-clause elements" );
472 					}
473 				}
474 				else {
475 					referencedFromElement = fromElement;
476 					joinAlias = extractAppliedAlias( dotNode );
477                     // TODO : temporary
478 					//      needed because currently persister is the one that
479                     // creates and renders the join fragments for inheritance
480 					//      hierarchies...
481 					if ( !joinAlias.equals( referencedFromElement.getTableAlias() ) ) {
482 						throw new InvalidWithClauseException( "with clause can only reference columns in the driving table" );
483 					}
484 				}
485 			}
486 			else if ( node instanceof ParameterNode ) {
487 				applyParameterSpecification( ( ( ParameterNode ) node ).getHqlParameterSpecification() );
488 			}
489 			else if ( node instanceof ParameterContainer ) {
490 				applyParameterSpecifications( ( ParameterContainer ) node );
491 			}
492 		}
493 
494 		private void applyParameterSpecifications(ParameterContainer parameterContainer) {
495 			if ( parameterContainer.hasEmbeddedParameters() ) {
496 				ParameterSpecification[] specs = parameterContainer.getEmbeddedParameters();
497 				for ( int i = 0; i < specs.length; i++ ) {
498 					applyParameterSpecification( specs[i] );
499 				}
500 			}
501 		}
502 
503 		private void applyParameterSpecification(ParameterSpecification paramSpec) {
504 			joinFragment.addEmbeddedParameter( paramSpec );
505 		}
506 
507 		private String extractAppliedAlias(DotNode dotNode) {
508 			return dotNode.getText().substring( 0, dotNode.getText().indexOf( '.' ) );
509 		}
510 
511 		public FromElement getReferencedFromElement() {
512 			return referencedFromElement;
513 		}
514 
515 		public String getJoinAlias() {
516 			return joinAlias;
517 		}
518 	}
519 
520 	/**
521 	 * Sets the current 'FROM' context.
522 	 *
523 	 * @param fromNode      The new 'FROM' context.
524 	 * @param inputFromNode The from node from the input AST.
525 	 */
526 	@Override
527     protected void pushFromClause(AST fromNode, AST inputFromNode) {
528 		FromClause newFromClause = ( FromClause ) fromNode;
529 		newFromClause.setParentFromClause( currentFromClause );
530 		currentFromClause = newFromClause;
531 	}
532 
533 	/**
534 	 * Returns to the previous 'FROM' context.
535 	 */
536 	private void popFromClause() {
537 		currentFromClause = currentFromClause.getParentFromClause();
538 	}
539 
540 	@Override
541     protected void lookupAlias(AST aliasRef)
542 			throws SemanticException {
543 		FromElement alias = currentFromClause.getFromElement( aliasRef.getText() );
544 		FromReferenceNode aliasRefNode = ( FromReferenceNode ) aliasRef;
545 		aliasRefNode.setFromElement( alias );
546 	}
547 
548 	@Override
549     protected void setImpliedJoinType(int joinType) {
550 		impliedJoinType = JoinProcessor.toHibernateJoinType( joinType );
551 	}
552 
553 	public JoinType getImpliedJoinType() {
554 		return impliedJoinType;
555 	}
556 
557 	@Override
558     protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
559 		DotNode dotNode = ( DotNode ) dot;
560 		FromReferenceNode lhs = dotNode.getLhs();
561 		AST rhs = lhs.getNextSibling();
562 		switch ( rhs.getType() ) {
563 			case SqlTokenTypes.ELEMENTS:
564 			case SqlTokenTypes.INDICES:
565                 if (LOG.isDebugEnabled()) LOG.debugf("lookupProperty() %s => %s(%s)",
566                                                      dotNode.getPath(),
567                                                      rhs.getText(),
568                                                      lhs.getPath());
569 				CollectionFunction f = ( CollectionFunction ) rhs;
570 				// Re-arrange the tree so that the collection function is the root and the lhs is the path.
571 				f.setFirstChild( lhs );
572 				lhs.setNextSibling( null );
573 				dotNode.setFirstChild( f );
574 				resolve( lhs );			// Don't forget to resolve the argument!
575 				f.resolve( inSelect );	// Resolve the collection function now.
576 				return f;
577 			default:
578 				// Resolve everything up to this dot, but don't resolve the placeholders yet.
579 				dotNode.resolveFirstChild();
580 				return dotNode;
581 		}
582 	}
583 
584 	@Override
585     protected boolean isNonQualifiedPropertyRef(AST ident) {
586 		final String identText = ident.getText();
587 		if ( currentFromClause.isFromElementAlias( identText ) ) {
588 			return false;
589 		}
590 
591 		List fromElements = currentFromClause.getExplicitFromElements();
592 		if ( fromElements.size() == 1 ) {
593 			final FromElement fromElement = ( FromElement ) fromElements.get( 0 );
594 			try {
595 				LOG.tracev( "Attempting to resolve property [{0}] as a non-qualified ref", identText );
596 				return fromElement.getPropertyMapping( identText ).toType( identText ) != null;
597 			}
598 			catch( QueryException e ) {
599 				// Should mean that no such property was found
600 			}
601 		}
602 
603 		return false;
604 	}
605 
606 	@Override
607 	protected AST lookupNonQualifiedProperty(AST property) throws SemanticException {
608 		final FromElement fromElement = ( FromElement ) currentFromClause.getExplicitFromElements().get( 0 );
609 		AST syntheticDotNode = generateSyntheticDotNodeForNonQualifiedPropertyRef( property, fromElement );
610 		return lookupProperty( syntheticDotNode, false, getCurrentClauseType() == HqlSqlTokenTypes.SELECT );
611 	}
612 
613 	private AST generateSyntheticDotNodeForNonQualifiedPropertyRef(AST property, FromElement fromElement) {
614 		AST dot = getASTFactory().create( DOT, "{non-qualified-property-ref}" );
615 		// TODO : better way?!?
616 		( ( DotNode ) dot ).setPropertyPath( ( ( FromReferenceNode ) property ).getPath() );
617 
618 		IdentNode syntheticAlias = ( IdentNode ) getASTFactory().create( IDENT, "{synthetic-alias}" );
619 		syntheticAlias.setFromElement( fromElement );
620 		syntheticAlias.setResolved();
621 
622 		dot.setFirstChild( syntheticAlias );
623 		dot.addChild( property );
624 
625 		return dot;
626 	}
627 
628 	@Override
629 	protected void processQuery(AST select, AST query) throws SemanticException {
630 		if ( LOG.isDebugEnabled() ) {
631 			LOG.debugf( "processQuery() : %s", query.toStringTree() );
632 		}
633 
634 		try {
635 			QueryNode qn = ( QueryNode ) query;
636 
637 			// Was there an explicit select expression?
638 			boolean explicitSelect = select != null && select.getNumberOfChildren() > 0;
639 
640 			if ( !explicitSelect ) {
641 				// No explicit select expression; render the id and properties
642 				// projection lists for every persister in the from clause into
643 				// a single 'token node'.
644 				//TODO: the only reason we need this stuff now is collection filters,
645 				//      we should get rid of derived select clause completely!
646 				createSelectClauseFromFromClause( qn );
647 			}
648 			else {
649 				// Use the explicitly declared select expression; determine the
650 				// return types indicated by each select token
651 				useSelectClause( select );
652 			}
653 
654 			// After that, process the JOINs.
655 			// Invoke a delegate to do the work, as this is farily complex.
656 			JoinProcessor joinProcessor = new JoinProcessor( this );
657 			joinProcessor.processJoins( qn );
658 
659 			// Attach any mapping-defined "ORDER BY" fragments
660 			Iterator itr = qn.getFromClause().getProjectionList().iterator();
661 			while ( itr.hasNext() ) {
662 				final FromElement fromElement = ( FromElement ) itr.next();
663 //			if ( fromElement.isFetch() && fromElement.isCollectionJoin() ) {
664 				if ( fromElement.isFetch() && fromElement.getQueryableCollection() != null ) {
665 					// Does the collection referenced by this FromElement
666 					// specify an order-by attribute?  If so, attach it to
667 					// the query's order-by
668 					if ( fromElement.getQueryableCollection().hasOrdering() ) {
669 						String orderByFragment = fromElement
670 								.getQueryableCollection()
671 								.getSQLOrderByString( fromElement.getCollectionTableAlias() );
672 						qn.getOrderByClause().addOrderFragment( orderByFragment );
673 					}
674 					if ( fromElement.getQueryableCollection().hasManyToManyOrdering() ) {
675 						String orderByFragment = fromElement.getQueryableCollection()
676 								.getManyToManyOrderByString( fromElement.getTableAlias() );
677 						qn.getOrderByClause().addOrderFragment( orderByFragment );
678 					}
679 				}
680 			}
681 		}
682 		finally {
683 			popFromClause();
684 		}
685 	}
686 
687 	protected void postProcessDML(RestrictableStatement statement) throws SemanticException {
688 		statement.getFromClause().resolve();
689 
690 		FromElement fromElement = ( FromElement ) statement.getFromClause().getFromElements().get( 0 );
691 		Queryable persister = fromElement.getQueryable();
692 		// Make #@%$^#^&# sure no alias is applied to the table name
693 		fromElement.setText( persister.getTableName() );
694 
695 //		// append any filter fragments; the EMPTY_MAP is used under the assumption that
696 //		// currently enabled filters should not affect this process
697 //		if ( persister.getDiscriminatorType() != null ) {
698 //			new SyntheticAndFactory( getASTFactory() ).addDiscriminatorWhereFragment(
699 //			        statement,
700 //			        persister,
701 //			        java.util.Collections.EMPTY_MAP,
702 //			        fromElement.getTableAlias()
703 //			);
704 //		}
705 		if ( persister.getDiscriminatorType() != null || ! queryTranslatorImpl.getEnabledFilters().isEmpty() ) {
706 			new SyntheticAndFactory( this ).addDiscriminatorWhereFragment(
707 			        statement,
708 			        persister,
709 			        queryTranslatorImpl.getEnabledFilters(),
710 			        fromElement.getTableAlias()
711 			);
712 		}
713 
714 	}
715 
716 	@Override
717     protected void postProcessUpdate(AST update) throws SemanticException {
718 		UpdateStatement updateStatement = ( UpdateStatement ) update;
719 
720 		postProcessDML( updateStatement );
721 	}
722 
723 	@Override
724     protected void postProcessDelete(AST delete) throws SemanticException {
725 		postProcessDML( ( DeleteStatement ) delete );
726 	}
727 
728 	@Override
729     protected void postProcessInsert(AST insert) throws SemanticException, QueryException {
730 		InsertStatement insertStatement = ( InsertStatement ) insert;
731 		insertStatement.validate();
732 
733 		SelectClause selectClause = insertStatement.getSelectClause();
734 		Queryable persister = insertStatement.getIntoClause().getQueryable();
735 
736 		if ( !insertStatement.getIntoClause().isExplicitIdInsertion() ) {
737 			// the insert did not explicitly reference the id.  See if
738 			//		1) that is allowed
739 			//		2) whether we need to alter the SQL tree to account for id
740 			final IdentifierGenerator generator = persister.getIdentifierGenerator();
741 			if ( !BulkInsertionCapableIdentifierGenerator.class.isInstance( generator ) ) {
742 				throw new QueryException(
743 						"Invalid identifier generator encountered for implicit id handling as part of bulk insertions"
744 				);
745 			}
746 			final BulkInsertionCapableIdentifierGenerator capableGenerator =
747 					BulkInsertionCapableIdentifierGenerator.class.cast( generator );
748 			if ( ! capableGenerator.supportsBulkInsertionIdentifierGeneration() ) {
749 				throw new QueryException(
750 						"Identifier generator reported it does not support implicit id handling as part of bulk insertions"
751 				);
752 			}
753 
754             final String fragment = capableGenerator.determineBulkInsertionIdentifierGenerationSelectFragment(
755 					sessionFactoryHelper.getFactory().getDialect()
756 			);
757 			if ( fragment != null ) {
758                 // we got a fragment from the generator, so alter the sql tree...
759                 //
760                 // first, wrap the fragment as a node
761                 AST fragmentNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, fragment );
762                 // next, rearrange the SQL tree to add the fragment node as the first select expression
763                 AST originalFirstSelectExprNode = selectClause.getFirstChild();
764                 selectClause.setFirstChild( fragmentNode );
765                 fragmentNode.setNextSibling( originalFirstSelectExprNode );
766                 // finally, prepend the id column name(s) to the insert-spec
767                 insertStatement.getIntoClause().prependIdColumnSpec();
768 			}
769 		}
770 
771 		final boolean includeVersionProperty = persister.isVersioned() &&
772 				!insertStatement.getIntoClause().isExplicitVersionInsertion() &&
773 				persister.isVersionPropertyInsertable();
774 		if ( includeVersionProperty ) {
775 			// We need to seed the version value as part of this bulk insert
776 			VersionType versionType = persister.getVersionType();
777 			AST versionValueNode = null;
778 
779 			if ( sessionFactoryHelper.getFactory().getDialect().supportsParametersInInsertSelect() ) {
780 				int sqlTypes[] = versionType.sqlTypes( sessionFactoryHelper.getFactory() );
781 				if ( sqlTypes == null || sqlTypes.length == 0 ) {
782 					throw new IllegalStateException( versionType.getClass() + ".sqlTypes() returns null or empty array" );
783 				}
784 				if ( sqlTypes.length > 1 ) {
785 					throw new IllegalStateException(
786 							versionType.getClass() +
787 									".sqlTypes() returns > 1 element; only single-valued versions are allowed."
788 					);
789 				}
790 				versionValueNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
791 				ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
792 				( ( ParameterNode ) versionValueNode ).setHqlParameterSpecification( paramSpec );
793 				parameters.add( 0, paramSpec );
794 
795 				if ( sessionFactoryHelper.getFactory().getDialect().requiresCastingOfParametersInSelectClause() ) {
796 					// we need to wrtap the param in a cast()
797 					MethodNode versionMethodNode = ( MethodNode ) getASTFactory().create( HqlSqlTokenTypes.METHOD_CALL, "(" );
798 					AST methodIdentNode = getASTFactory().create( HqlSqlTokenTypes.IDENT, "cast" );
799 					versionMethodNode.addChild( methodIdentNode );
800 					versionMethodNode.initializeMethodNode(methodIdentNode, true );
801 					AST castExprListNode = getASTFactory().create( HqlSqlTokenTypes.EXPR_LIST, "exprList" );
802 					methodIdentNode.setNextSibling( castExprListNode );
803 					castExprListNode.addChild( versionValueNode );
804 					versionValueNode.setNextSibling(
805 							getASTFactory().create(
806 									HqlSqlTokenTypes.IDENT,
807 									sessionFactoryHelper.getFactory().getDialect().getTypeName( sqlTypes[0] ) )
808 					);
809 					processFunction( versionMethodNode, true );
810 					versionValueNode = versionMethodNode;
811 				}
812 			}
813 			else {
814 				if ( isIntegral( versionType ) ) {
815 					try {
816 						Object seedValue = versionType.seed( null );
817 						versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, seedValue.toString() );
818 					}
819 					catch( Throwable t ) {
820 						throw new QueryException( "could not determine seed value for version on bulk insert [" + versionType + "]" );
821 					}
822 				}
823 				else if ( isDatabaseGeneratedTimestamp( versionType ) ) {
824 					String functionName = sessionFactoryHelper.getFactory().getDialect().getCurrentTimestampSQLFunctionName();
825 					versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, functionName );
826 				}
827 				else {
828 					throw new QueryException( "cannot handle version type [" + versionType + "] on bulk inserts with dialects not supporting parameters in insert-select statements" );
829 				}
830 			}
831 
832 			AST currentFirstSelectExprNode = selectClause.getFirstChild();
833 			selectClause.setFirstChild( versionValueNode );
834 			versionValueNode.setNextSibling( currentFirstSelectExprNode );
835 
836 			insertStatement.getIntoClause().prependVersionColumnSpec();
837 		}
838 
839 		if ( insertStatement.getIntoClause().isDiscriminated() ) {
840 			String sqlValue = insertStatement.getIntoClause().getQueryable().getDiscriminatorSQLValue();
841 			AST discrimValue = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, sqlValue );
842 			insertStatement.getSelectClause().addChild( discrimValue );
843 		}
844 
845 	}
846 
847 	private boolean isDatabaseGeneratedTimestamp(Type type) {
848 		// currently only the Hibernate-supplied DbTimestampType is supported here
849 		return DbTimestampType.class.isAssignableFrom( type.getClass() );
850 	}
851 
852 	private boolean isIntegral(Type type) {
853 		return Long.class.isAssignableFrom( type.getReturnedClass() )
854 		       || Integer.class.isAssignableFrom( type.getReturnedClass() )
855 		       || long.class.isAssignableFrom( type.getReturnedClass() )
856 		       || int.class.isAssignableFrom( type.getReturnedClass() );
857 	}
858 
859 	private void useSelectClause(AST select) throws SemanticException {
860 		selectClause = ( SelectClause ) select;
861 		selectClause.initializeExplicitSelectClause( currentFromClause );
862 	}
863 
864 	private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticException {
865 		AST select = astFactory.create( SELECT_CLAUSE, "{derived select clause}" );
866 		AST sibling = qn.getFromClause();
867 		qn.setFirstChild( select );
868 		select.setNextSibling( sibling );
869 		selectClause = ( SelectClause ) select;
870 		selectClause.initializeDerivedSelectClause( currentFromClause );
871 		LOG.debug( "Derived SELECT clause created." );
872 	}
873 
874 	@Override
875     protected void resolve(AST node) throws SemanticException {
876 		if ( node != null ) {
877 			// This is called when it's time to fully resolve a path expression.
878 			ResolvableNode r = ( ResolvableNode ) node;
879 			if ( isInFunctionCall() ) {
880 				r.resolveInFunctionCall( false, true );
881 			}
882 			else {
883 				r.resolve( false, true );	// Generate implicit joins, only if necessary.
884 			}
885 		}
886 	}
887 
888 	@Override
889     protected void resolveSelectExpression(AST node) throws SemanticException {
890 		// This is called when it's time to fully resolve a path expression.
891 		int type = node.getType();
892 		switch ( type ) {
893 			case DOT: {
894 				DotNode dot = ( DotNode ) node;
895 				dot.resolveSelectExpression();
896 				break;
897 			}
898 			case ALIAS_REF: {
899 				// Notify the FROM element that it is being referenced by the select.
900 				FromReferenceNode aliasRefNode = ( FromReferenceNode ) node;
901 				//aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here?
902 				aliasRefNode.resolve( false, false ); //TODO: is it kosher to do it here?
903 				FromElement fromElement = aliasRefNode.getFromElement();
904 				if ( fromElement != null ) {
905 					fromElement.setIncludeSubclasses( true );
906 				}
907 				break;
908 			}
909 			default: {
910 				break;
911 			}
912 		}
913 	}
914 
915 	@Override
916     protected void beforeSelectClause() throws SemanticException {
917 		// Turn off includeSubclasses on all FromElements.
918 		FromClause from = getCurrentFromClause();
919 		List fromElements = from.getFromElements();
920 		for ( Iterator iterator = fromElements.iterator(); iterator.hasNext(); ) {
921 			FromElement fromElement = ( FromElement ) iterator.next();
922 			fromElement.setIncludeSubclasses( false );
923 		}
924 	}
925 
926 	@Override
927     protected AST generatePositionalParameter(AST inputNode) throws SemanticException {
928 		if ( namedParameters.size() > 0 ) {
929 			throw new SemanticException( "cannot define positional parameter after any named parameters have been defined" );
930 		}
931 		LOG.warnf(
932 				"[DEPRECATION] Encountered positional parameter near line %s, column %s.  Positional parameter " +
933 						"are considered deprecated; use named parameters or JPA-style positional parameters instead.",
934 				inputNode.getLine(),
935 				inputNode.getColumn()
936 		);
937 		ParameterNode parameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
938 		PositionalParameterSpecification paramSpec = new PositionalParameterSpecification(
939 				inputNode.getLine(),
940 		        inputNode.getColumn(),
941 				positionalParameterCount++
942 		);
943 		parameter.setHqlParameterSpecification( paramSpec );
944 		parameters.add( paramSpec );
945 		return parameter;
946 	}
947 
948 	@Override
949     protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {
950 		String name = nameNode.getText();
951 		trackNamedParameterPositions( name );
952 
953 		// create the node initially with the param name so that it shows
954 		// appropriately in the "original text" attribute
955 		ParameterNode parameter = ( ParameterNode ) astFactory.create( NAMED_PARAM, name );
956 		parameter.setText( "?" );
957 
958 		NamedParameterSpecification paramSpec = new NamedParameterSpecification(
959 				delimiterNode.getLine(),
960 		        delimiterNode.getColumn(),
961 				name
962 		);
963 		parameter.setHqlParameterSpecification( paramSpec );
964 		parameters.add( paramSpec );
965 		return parameter;
966 	}
967 
968 	private void trackNamedParameterPositions(String name) {
969 		Integer loc = parameterCount++;
970 		Object o = namedParameters.get( name );
971 		if ( o == null ) {
972 			namedParameters.put( name, loc );
973 		}
974 		else if ( o instanceof Integer ) {
975 			ArrayList list = new ArrayList( 4 );
976 			list.add( o );
977 			list.add( loc );
978 			namedParameters.put( name, list );
979 		}
980 		else {
981 			( ( ArrayList ) o ).add( loc );
982 		}
983 	}
984 
985 	@Override
986     protected void processConstant(AST constant) throws SemanticException {
987 		literalProcessor.processConstant( constant, true );  // Use the delegate, resolve identifiers as FROM element aliases.
988 	}
989 
990 	@Override
991     protected void processBoolean(AST constant) throws SemanticException {
992 		literalProcessor.processBoolean( constant );  // Use the delegate.
993 	}
994 
995 	@Override
996     protected void processNumericLiteral(AST literal) {
997 		literalProcessor.processNumeric( literal );
998 	}
999 
1000 	@Override
1001     protected void processIndex(AST indexOp) throws SemanticException {
1002 		IndexNode indexNode = ( IndexNode ) indexOp;
1003 		indexNode.resolve( true, true );
1004 	}
1005 
1006 	@Override
1007     protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
1008 		MethodNode methodNode = ( MethodNode ) functionCall;
1009 		methodNode.resolve( inSelect );
1010 	}
1011 
1012 	@Override
1013 	protected void processCastFunction(AST castFunctionCall, boolean inSelect) throws SemanticException {
1014 		CastFunctionNode castFunctionNode = (CastFunctionNode) castFunctionCall;
1015 		castFunctionNode.resolve( inSelect );
1016 	}
1017 
1018 	@Override
1019 	protected void processAggregation(AST node, boolean inSelect) throws SemanticException {
1020 		AggregateNode aggregateNode = (AggregateNode) node;
1021 		aggregateNode.resolve();
1022 	}
1023 
1024 	@Override
1025     protected void processConstructor(AST constructor) throws SemanticException {
1026 		ConstructorNode constructorNode = ( ConstructorNode ) constructor;
1027 		constructorNode.prepare();
1028 	}
1029 
1030     @Override
1031     protected void setAlias(AST selectExpr, AST ident) {
1032         ((SelectExpression) selectExpr).setAlias(ident.getText());
1033 		// only put the alias (i.e., result variable) in selectExpressionsByResultVariable
1034 		// if is not defined in a subquery.
1035 		if ( ! isSubQuery() ) {
1036 			selectExpressionsByResultVariable.put( ident.getText(), ( SelectExpression ) selectExpr );
1037 		}
1038     }
1039 
1040 	@Override
1041     protected boolean isOrderExpressionResultVariableRef(AST orderExpressionNode) throws SemanticException {
1042 		// ORDER BY is not supported in a subquery
1043 		// TODO: should an exception be thrown if an ORDER BY is in a subquery?
1044 		if ( ! isSubQuery() &&
1045 				orderExpressionNode.getType() == IDENT &&
1046 				selectExpressionsByResultVariable.containsKey( orderExpressionNode.getText() ) ) {
1047 			return true;
1048 		}
1049 		return false;
1050 	}
1051 
1052 	@Override
1053     protected void handleResultVariableRef(AST resultVariableRef) throws SemanticException {
1054 		if ( isSubQuery() ) {
1055 			throw new SemanticException(
1056 					"References to result variables in subqueries are not supported."
1057 			);
1058 		}
1059 		( ( ResultVariableRefNode ) resultVariableRef ).setSelectExpression(
1060 				selectExpressionsByResultVariable.get( resultVariableRef.getText() )
1061 		);
1062 	}
1063 
1064 	/**
1065 	 * Returns the locations of all occurrences of the named parameter.
1066 	 */
1067 	public int[] getNamedParameterLocations(String name) throws QueryException {
1068 		Object o = namedParameters.get( name );
1069 		if ( o == null ) {
1070 			QueryException qe = new QueryException( QueryTranslator.ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR + name );
1071 			qe.setQueryString( queryTranslatorImpl.getQueryString() );
1072 			throw qe;
1073 		}
1074 		if ( o instanceof Integer ) {
1075 			return new int[]{( ( Integer ) o ).intValue()};
1076 		}
1077 		else {
1078 			return ArrayHelper.toIntArray( (ArrayList) o );
1079 		}
1080 	}
1081 
1082 	public void addQuerySpaces(Serializable[] spaces) {
1083 		querySpaces.addAll( Arrays.asList( spaces ) );
1084 	}
1085 
1086 	public Type[] getReturnTypes() {
1087 		return selectClause.getQueryReturnTypes();
1088 	}
1089 
1090 	public String[] getReturnAliases() {
1091 		return selectClause.getQueryReturnAliases();
1092 	}
1093 
1094 	public SelectClause getSelectClause() {
1095 		return selectClause;
1096 	}
1097 
1098 	public FromClause getFinalFromClause() {
1099 		FromClause top = currentFromClause;
1100 		while ( top.getParentFromClause() != null ) {
1101 			top = top.getParentFromClause();
1102 		}
1103 		return top;
1104 	}
1105 
1106 	public boolean isShallowQuery() {
1107 		// select clauses for insert statements should alwasy be treated as shallow
1108 		return getStatementType() == INSERT || queryTranslatorImpl.isShallowQuery();
1109 	}
1110 
1111 	public Map getEnabledFilters() {
1112 		return queryTranslatorImpl.getEnabledFilters();
1113 	}
1114 
1115 	public LiteralProcessor getLiteralProcessor() {
1116 		return literalProcessor;
1117 	}
1118 
1119 	public ASTPrinter getASTPrinter() {
1120 		return printer;
1121 	}
1122 
1123 	public ArrayList getParameters() {
1124 		return parameters;
1125 	}
1126 
1127 	public int getNumberOfParametersInSetClause() {
1128 		return numberOfParametersInSetClause;
1129 	}
1130 
1131 	@Override
1132     protected void evaluateAssignment(AST eq) throws SemanticException {
1133 		prepareLogicOperator( eq );
1134 		Queryable persister = getCurrentFromClause().getFromElement().getQueryable();
1135 		evaluateAssignment( eq, persister, -1 );
1136 	}
1137 
1138 	private void evaluateAssignment(AST eq, Queryable persister, int targetIndex) {
1139 		if ( persister.isMultiTable() ) {
1140 			// no need to even collect this information if the persister is considered multi-table
1141 			AssignmentSpecification specification = new AssignmentSpecification( eq, persister );
1142 			if ( targetIndex >= 0 ) {
1143 				assignmentSpecifications.add( targetIndex, specification );
1144 			}
1145 			else {
1146 				assignmentSpecifications.add( specification );
1147 			}
1148 			numberOfParametersInSetClause += specification.getParameters().length;
1149 		}
1150 	}
1151 
1152 	public ArrayList getAssignmentSpecifications() {
1153 		return assignmentSpecifications;
1154 	}
1155 
1156 	@Override
1157     protected AST createIntoClause(String path, AST propertySpec) throws SemanticException {
1158 		Queryable persister = ( Queryable ) getSessionFactoryHelper().requireClassPersister( path );
1159 
1160 		IntoClause intoClause = ( IntoClause ) getASTFactory().create( INTO, persister.getEntityName() );
1161 		intoClause.setFirstChild( propertySpec );
1162 		intoClause.initialize( persister );
1163 
1164 		addQuerySpaces( persister.getQuerySpaces() );
1165 
1166 		return intoClause;
1167 	}
1168 
1169 	@Override
1170     protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticException {
1171 		UpdateStatement updateStatement = ( UpdateStatement ) updateNode;
1172 		FromClause fromClause = updateStatement.getFromClause();
1173 		if ( versioned != null ) {
1174 			// Make sure that the persister is versioned
1175 			Queryable persister = fromClause.getFromElement().getQueryable();
1176 			if ( !persister.isVersioned() ) {
1177 				throw new SemanticException( "increment option specified for update of non-versioned entity" );
1178 			}
1179 
1180 			VersionType versionType = persister.getVersionType();
1181 			if ( versionType instanceof UserVersionType ) {
1182 				throw new SemanticException( "user-defined version types not supported for increment option" );
1183 			}
1184 
1185 			AST eq = getASTFactory().create( HqlSqlTokenTypes.EQ, "=" );
1186 			AST versionPropertyNode = generateVersionPropertyNode( persister );
1187 
1188 			eq.setFirstChild( versionPropertyNode );
1189 
1190 			AST versionIncrementNode = null;
1191 			if ( isTimestampBasedVersion( versionType ) ) {
1192 				versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
1193 				ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
1194 				( ( ParameterNode ) versionIncrementNode ).setHqlParameterSpecification( paramSpec );
1195 				parameters.add( 0, paramSpec );
1196 			}
1197 			else {
1198 				// Not possible to simply re-use the versionPropertyNode here as it causes
1199 				// OOM errors due to circularity :(
1200 				versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PLUS, "+" );
1201 				versionIncrementNode.setFirstChild( generateVersionPropertyNode( persister ) );
1202 				versionIncrementNode.addChild( getASTFactory().create( HqlSqlTokenTypes.IDENT, "1" ) );
1203 			}
1204 
1205 			eq.addChild( versionIncrementNode );
1206 
1207 			evaluateAssignment( eq, persister, 0 );
1208 
1209 			AST setClause = updateStatement.getSetClause();
1210 			AST currentFirstSetElement = setClause.getFirstChild();
1211 			setClause.setFirstChild( eq );
1212 			eq.setNextSibling( currentFirstSetElement );
1213 		}
1214 	}
1215 
1216 	private boolean isTimestampBasedVersion(VersionType versionType) {
1217 		final Class javaType = versionType.getReturnedClass();
1218 		return Date.class.isAssignableFrom( javaType )
1219 				|| Calendar.class.isAssignableFrom( javaType );
1220 	}
1221 
1222 	private AST generateVersionPropertyNode(Queryable persister) throws SemanticException {
1223 		String versionPropertyName = persister.getPropertyNames()[ persister.getVersionProperty() ];
1224 		AST versionPropertyRef = getASTFactory().create( HqlSqlTokenTypes.IDENT, versionPropertyName );
1225 		AST versionPropertyNode = lookupNonQualifiedProperty( versionPropertyRef );
1226 		resolve( versionPropertyNode );
1227 		return versionPropertyNode;
1228 	}
1229 
1230 	@Override
1231     protected void prepareLogicOperator(AST operator) throws SemanticException {
1232 		( ( OperatorNode ) operator ).initialize();
1233 	}
1234 
1235 	@Override
1236     protected void prepareArithmeticOperator(AST operator) throws SemanticException {
1237 		( ( OperatorNode ) operator ).initialize();
1238 	}
1239 
1240 	@Override
1241     protected void validateMapPropertyExpression(AST node) throws SemanticException {
1242 		try {
1243 			FromReferenceNode fromReferenceNode = (FromReferenceNode) node;
1244 			QueryableCollection collectionPersister = fromReferenceNode.getFromElement().getQueryableCollection();
1245 			if ( ! Map.class.isAssignableFrom( collectionPersister.getCollectionType().getReturnedClass() ) ) {
1246 				throw new SemanticException( "node did not reference a map" );
1247 			}
1248 		}
1249 		catch ( SemanticException se ) {
1250 			throw se;
1251 		}
1252 		catch ( Throwable t ) {
1253 			throw new SemanticException( "node did not reference a map" );
1254 		}
1255 	}
1256 
1257 	public static void panic() {
1258 		throw new QueryException( "TreeWalker: panic" );
1259 	}
1260 }