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.ast;
26
27 import java.io.Serializable;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import org.hibernate.QueryException;
40 import org.hibernate.HibernateException;
41 import org.hibernate.engine.JoinSequence;
42 import org.hibernate.engine.ParameterBinder;
43 import org.hibernate.engine.SessionFactoryImplementor;
44 import org.hibernate.hql.QueryTranslator;
45 import org.hibernate.hql.antlr.HqlSqlBaseWalker;
46 import org.hibernate.hql.antlr.HqlSqlTokenTypes;
47 import org.hibernate.hql.antlr.HqlTokenTypes;
48 import org.hibernate.hql.antlr.SqlTokenTypes;
49 import org.hibernate.hql.ast.tree.AssignmentSpecification;
50 import org.hibernate.hql.ast.tree.CollectionFunction;
51 import org.hibernate.hql.ast.tree.ConstructorNode;
52 import org.hibernate.hql.ast.tree.DeleteStatement;
53 import org.hibernate.hql.ast.tree.DotNode;
54 import org.hibernate.hql.ast.tree.FromClause;
55 import org.hibernate.hql.ast.tree.FromElement;
56 import org.hibernate.hql.ast.tree.FromReferenceNode;
57 import org.hibernate.hql.ast.tree.IdentNode;
58 import org.hibernate.hql.ast.tree.IndexNode;
59 import org.hibernate.hql.ast.tree.InsertStatement;
60 import org.hibernate.hql.ast.tree.IntoClause;
61 import org.hibernate.hql.ast.tree.MethodNode;
62 import org.hibernate.hql.ast.tree.ParameterNode;
63 import org.hibernate.hql.ast.tree.QueryNode;
64 import org.hibernate.hql.ast.tree.ResolvableNode;
65 import org.hibernate.hql.ast.tree.RestrictableStatement;
66 import org.hibernate.hql.ast.tree.SelectClause;
67 import org.hibernate.hql.ast.tree.SelectExpression;
68 import org.hibernate.hql.ast.tree.UpdateStatement;
69 import org.hibernate.hql.ast.tree.Node;
70 import org.hibernate.hql.ast.tree.OperatorNode;
71 import org.hibernate.hql.ast.util.ASTPrinter;
72 import org.hibernate.hql.ast.util.ASTUtil;
73 import org.hibernate.hql.ast.util.AliasGenerator;
74 import org.hibernate.hql.ast.util.JoinProcessor;
75 import org.hibernate.hql.ast.util.LiteralProcessor;
76 import org.hibernate.hql.ast.util.SessionFactoryHelper;
77 import org.hibernate.hql.ast.util.SyntheticAndFactory;
78 import org.hibernate.hql.ast.util.NodeTraverser;
79 import org.hibernate.id.IdentifierGenerator;
80 import org.hibernate.id.PostInsertIdentifierGenerator;
81 import org.hibernate.id.SequenceGenerator;
82 import org.hibernate.param.NamedParameterSpecification;
83 import org.hibernate.param.ParameterSpecification;
84 import org.hibernate.param.PositionalParameterSpecification;
85 import org.hibernate.param.VersionTypeSeedParameterSpecification;
86 import org.hibernate.param.CollectionFilterKeyParameterSpecification;
87 import org.hibernate.persister.collection.QueryableCollection;
88 import org.hibernate.persister.entity.Queryable;
89 import org.hibernate.sql.JoinFragment;
90 import org.hibernate.type.AssociationType;
91 import org.hibernate.type.Type;
92 import org.hibernate.type.VersionType;
93 import org.hibernate.type.DbTimestampType;
94 import org.hibernate.usertype.UserVersionType;
95 import org.hibernate.util.ArrayHelper;
96
97 import antlr.ASTFactory;
98 import antlr.RecognitionException;
99 import antlr.SemanticException;
100 import antlr.collections.AST;
101
102 /**
103 * Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase).
104 * <ul>
105 * <li>Isolates the Hibernate API-specific code from the ANTLR generated code.</li>
106 * <li>Handles the SQL framgents generated by the persisters in order to create the SELECT and FROM clauses,
107 * taking into account the joins and projections that are implied by the mappings (persister/queryable).</li>
108 * <li>Uses SqlASTFactory to create customized AST nodes.</li>
109 * </ul>
110 *
111 * @see SqlASTFactory
112 */
113 public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, ParameterBinder.NamedParameterSource {
114 private static final Logger log = LoggerFactory.getLogger( HqlSqlWalker.class );
115
116 private final QueryTranslatorImpl queryTranslatorImpl;
117 private final HqlParser hqlParser;
118 private final SessionFactoryHelper sessionFactoryHelper;
119 private final Map tokenReplacements;
120 private final AliasGenerator aliasGenerator = new AliasGenerator();
121 private final LiteralProcessor literalProcessor;
122 private final ParseErrorHandler parseErrorHandler;
123 private final ASTPrinter printer;
124 private final String collectionFilterRole;
125
126 private FromClause currentFromClause = null;
127 private SelectClause selectClause;
128
129 private Set querySpaces = new HashSet();
130
131 private int parameterCount;
132 private Map namedParameters = new HashMap();
133 private ArrayList parameters = new ArrayList();
134 private int numberOfParametersInSetClause;
135 private int positionalParameterCount;
136
137 private ArrayList assignmentSpecifications = new ArrayList();
138
139 private int impliedJoinType;
140
141 /**
142 * Create a new tree transformer.
143 *
144 * @param qti Back pointer to the query translator implementation that is using this tree transform.
145 * @param sfi The session factory implementor where the Hibernate mappings can be found.
146 * @param parser A reference to the phase-1 parser
147 * @param tokenReplacements Registers the token replacement map with the walker. This map will
148 * be used to substitute function names and constants.
149 * @param collectionRole The collection role name of the collection used as the basis for the
150 * filter, NULL if this is not a collection filter compilation.
151 */
152 public HqlSqlWalker(
153 QueryTranslatorImpl qti,
154 SessionFactoryImplementor sfi,
155 HqlParser parser,
156 Map tokenReplacements,
157 String collectionRole) {
158 setASTFactory( new SqlASTFactory( this ) );
159 // Initialize the error handling delegate.
160 this.parseErrorHandler = new ErrorCounter();
161 this.queryTranslatorImpl = qti;
162 this.sessionFactoryHelper = new SessionFactoryHelper( sfi );
163 this.literalProcessor = new LiteralProcessor( this );
164 this.tokenReplacements = tokenReplacements;
165 this.collectionFilterRole = collectionRole;
166 this.hqlParser = parser;
167 this.printer = new ASTPrinter( SqlTokenTypes.class );
168 }
169
170
171 protected void prepareFromClauseInputTree(AST fromClauseInput) {
172 if ( !isSubQuery() ) {
173 // // inject param specifications to account for dynamic filter param values
174 // if ( ! getEnabledFilters().isEmpty() ) {
175 // Iterator filterItr = getEnabledFilters().values().iterator();
176 // while ( filterItr.hasNext() ) {
177 // FilterImpl filter = ( FilterImpl ) filterItr.next();
178 // if ( ! filter.getFilterDefinition().getParameterNames().isEmpty() ) {
179 // Iterator paramItr = filter.getFilterDefinition().getParameterNames().iterator();
180 // while ( paramItr.hasNext() ) {
181 // String parameterName = ( String ) paramItr.next();
182 // // currently param filters *only* work with single-column parameter types;
183 // // if that limitation is ever lifted, this logic will need to change to account for that
184 // ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
185 // DynamicFilterParameterSpecification paramSpec = new DynamicFilterParameterSpecification(
186 // filter.getName(),
187 // parameterName,
188 // filter.getFilterDefinition().getParameterType( parameterName ),
189 // positionalParameterCount++
190 // );
191 // collectionFilterKeyParameter.setHqlParameterSpecification( paramSpec );
192 // parameters.add( paramSpec );
193 // }
194 // }
195 // }
196 // }
197
198 if ( isFilter() ) {
199 // Handle collection-fiter compilation.
200 // IMPORTANT NOTE: This is modifying the INPUT (HQL) tree, not the output tree!
201 QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole );
202 Type collectionElementType = persister.getElementType();
203 if ( !collectionElementType.isEntityType() ) {
204 throw new QueryException( "collection of values in filter: this" );
205 }
206
207 String collectionElementEntityName = persister.getElementPersister().getEntityName();
208 ASTFactory inputAstFactory = hqlParser.getASTFactory();
209 AST fromElement = ASTUtil.create( inputAstFactory, HqlTokenTypes.FILTER_ENTITY, collectionElementEntityName );
210 ASTUtil.createSibling( inputAstFactory, HqlTokenTypes.ALIAS, "this", fromElement );
211 fromClauseInput.addChild( fromElement );
212 // Show the modified AST.
213 if ( log.isDebugEnabled() ) {
214 log.debug( "prepareFromClauseInputTree() : Filter - Added 'this' as a from element..." );
215 }
216 queryTranslatorImpl.showHqlAst( hqlParser.getAST() );
217
218 // Create a parameter specification for the collection filter...
219 Type collectionFilterKeyType = sessionFactoryHelper.requireQueryableCollection( collectionFilterRole ).getKeyType();
220 ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
221 CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification(
222 collectionFilterRole, collectionFilterKeyType, positionalParameterCount++
223 );
224 collectionFilterKeyParameter.setHqlParameterSpecification( collectionFilterKeyParameterSpec );
225 parameters.add( collectionFilterKeyParameterSpec );
226 }
227 }
228 }
229
230 public boolean isFilter() {
231 return collectionFilterRole != null;
232 }
233
234 public SessionFactoryHelper getSessionFactoryHelper() {
235 return sessionFactoryHelper;
236 }
237
238 public Map getTokenReplacements() {
239 return tokenReplacements;
240 }
241
242 public AliasGenerator getAliasGenerator() {
243 return aliasGenerator;
244 }
245
246 public FromClause getCurrentFromClause() {
247 return currentFromClause;
248 }
249
250 public ParseErrorHandler getParseErrorHandler() {
251 return parseErrorHandler;
252 }
253
254 public void reportError(RecognitionException e) {
255 parseErrorHandler.reportError( e ); // Use the delegate.
256 }
257
258 public void reportError(String s) {
259 parseErrorHandler.reportError( s ); // Use the delegate.
260 }
261
262 public void reportWarning(String s) {
263 parseErrorHandler.reportWarning( s );
264 }
265
266 /**
267 * Returns the set of unique query spaces (a.k.a.
268 * table names) that occurred in the query.
269 *
270 * @return A set of table names (Strings).
271 */
272 public Set getQuerySpaces() {
273 return querySpaces;
274 }
275
276 protected AST createFromElement(String path, AST alias, AST propertyFetch) throws SemanticException {
277 FromElement fromElement = currentFromClause.addFromElement( path, alias );
278 fromElement.setAllPropertyFetch(propertyFetch!=null);
279 return fromElement;
280 }
281
282 protected AST createFromFilterElement(AST filterEntity, AST alias) throws SemanticException {
283 FromElement fromElement = currentFromClause.addFromElement( filterEntity.getText(), alias );
284 FromClause fromClause = fromElement.getFromClause();
285 QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole );
286 // Get the names of the columns used to link between the collection
287 // owner and the collection elements.
288 String[] keyColumnNames = persister.getKeyColumnNames();
289 String fkTableAlias = persister.isOneToMany()
290 ? fromElement.getTableAlias()
291 : fromClause.getAliasGenerator().createName( collectionFilterRole );
292 JoinSequence join = sessionFactoryHelper.createJoinSequence();
293 join.setRoot( persister, fkTableAlias );
294 if ( !persister.isOneToMany() ) {
295 join.addJoin( ( AssociationType ) persister.getElementType(),
296 fromElement.getTableAlias(),
297 JoinFragment.INNER_JOIN,
298 persister.getElementColumnNames( fkTableAlias ) );
299 }
300 join.addCondition( fkTableAlias, keyColumnNames, " = ?" );
301 fromElement.setJoinSequence( join );
302 fromElement.setFilter( true );
303 if ( log.isDebugEnabled() ) {
304 log.debug( "createFromFilterElement() : processed filter FROM element." );
305 }
306 return fromElement;
307 }
308
309 protected void createFromJoinElement(
310 AST path,
311 AST alias,
312 int joinType,
313 AST fetchNode,
314 AST propertyFetch,
315 AST with) throws SemanticException {
316 boolean fetch = fetchNode != null;
317 if ( fetch && isSubQuery() ) {
318 throw new QueryException( "fetch not allowed in subquery from-elements" );
319 }
320 // The path AST should be a DotNode, and it should have been evaluated already.
321 if ( path.getType() != SqlTokenTypes.DOT ) {
322 throw new SemanticException( "Path expected for join!" );
323 }
324 DotNode dot = ( DotNode ) path;
325 int hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType );
326 dot.setJoinType( hibernateJoinType ); // Tell the dot node about the join type.
327 dot.setFetch( fetch );
328 // Generate an explicit join for the root dot node. The implied joins will be collected and passed up
329 // to the root dot node.
330 dot.resolve( true, false, alias == null ? null : alias.getText() );
331 FromElement fromElement = dot.getImpliedJoin();
332 fromElement.setAllPropertyFetch(propertyFetch!=null);
333
334 if ( with != null ) {
335 if ( fetch ) {
336 throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
337 }
338 handleWithFragment( fromElement, with );
339 }
340
341 if ( log.isDebugEnabled() ) {
342 log.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );
343 }
344 }
345
346 private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException
347 {
348 try {
349 withClause( hqlWithNode );
350 AST hqlSqlWithNode = returnAST;
351 if ( log.isDebugEnabled() ) {
352 log.debug( "handleWithFragment() : " + getASTPrinter().showAsString( hqlSqlWithNode, "-- with clause --" ) );
353 }
354 WithClauseVisitor visitor = new WithClauseVisitor();
355 NodeTraverser traverser = new NodeTraverser( visitor );
356 traverser.traverseDepthFirst( hqlSqlWithNode );
357 FromElement referencedFromElement = visitor.getReferencedFromElement();
358 if ( referencedFromElement != fromElement ) {
359 throw new InvalidWithClauseException( "with-clause expressions did not reference from-clause element to which the with-clause was associated" );
360 }
361 SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() );
362 sql.whereExpr( hqlSqlWithNode.getFirstChild() );
363 fromElement.setWithClauseFragment( visitor.getJoinAlias(), "(" + sql.getSQL() + ")" );
364
365 }
366 catch( SemanticException e ) {
367 throw e;
368 }
369 catch( InvalidWithClauseException e ) {
370 throw e;
371 }
372 catch ( Exception e) {
373 throw new SemanticException( e.getMessage() );
374 }
375 }
376
377 private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy {
378 private FromElement referencedFromElement;
379 private String joinAlias;
380
381 public void visit(AST node) {
382 // todo : currently expects that the individual with expressions apply to the same sql table join.
383 // This may not be the case for joined-subclass where the property values
384 // might be coming from different tables in the joined hierarchy. At some
385 // point we should expand this to support that capability. However, that has
386 // some difficulties:
387 // 1) the biggest is how to handle ORs when the individual comparisons are
388 // linked to different sql joins.
389 // 2) here we would need to track each comparison individually, along with
390 // the join alias to which it applies and then pass that information
391 // back to the FromElement so it can pass it along to the JoinSequence
392
393 if ( node instanceof DotNode ) {
394 DotNode dotNode = ( DotNode ) node;
395 FromElement fromElement = dotNode.getFromElement();
396 if ( referencedFromElement != null ) {
397 if ( fromElement != referencedFromElement ) {
398 throw new HibernateException( "with-clause referenced two different from-clause elements" );
399 }
400 }
401 else {
402 referencedFromElement = fromElement;
403 joinAlias = extractAppliedAlias( dotNode );
404 // todo : temporary
405 // needed because currently persister is the one that
406 // creates and renders the join fragments for inheritence
407 // hierarchies...
408 if ( !joinAlias.equals( referencedFromElement.getTableAlias() ) ) {
409 throw new InvalidWithClauseException( "with clause can only reference columns in the driving table" );
410 }
411 }
412 }
413 }
414
415 private String extractAppliedAlias(DotNode dotNode) {
416 return dotNode.getText().substring( 0, dotNode.getText().indexOf( '.' ) );
417 }
418
419 public FromElement getReferencedFromElement() {
420 return referencedFromElement;
421 }
422
423 public String getJoinAlias() {
424 return joinAlias;
425 }
426 }
427
428 /**
429 * Sets the current 'FROM' context.
430 *
431 * @param fromNode The new 'FROM' context.
432 * @param inputFromNode The from node from the input AST.
433 */
434 protected void pushFromClause(AST fromNode, AST inputFromNode) {
435 FromClause newFromClause = ( FromClause ) fromNode;
436 newFromClause.setParentFromClause( currentFromClause );
437 currentFromClause = newFromClause;
438 }
439
440 /**
441 * Returns to the previous 'FROM' context.
442 */
443 private void popFromClause() {
444 currentFromClause = currentFromClause.getParentFromClause();
445 }
446
447 protected void lookupAlias(AST aliasRef)
448 throws SemanticException {
449 FromElement alias = currentFromClause.getFromElement( aliasRef.getText() );
450 FromReferenceNode aliasRefNode = ( FromReferenceNode ) aliasRef;
451 aliasRefNode.setFromElement( alias );
452 }
453
454 protected void setImpliedJoinType(int joinType) {
455 impliedJoinType = JoinProcessor.toHibernateJoinType( joinType );
456 }
457
458 public int getImpliedJoinType() {
459 return impliedJoinType;
460 }
461
462 protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
463 DotNode dotNode = ( DotNode ) dot;
464 FromReferenceNode lhs = dotNode.getLhs();
465 AST rhs = lhs.getNextSibling();
466 switch ( rhs.getType() ) {
467 case SqlTokenTypes.ELEMENTS:
468 case SqlTokenTypes.INDICES:
469 if ( log.isDebugEnabled() ) {
470 log.debug( "lookupProperty() " + dotNode.getPath() + " => " + rhs.getText() + "(" + lhs.getPath() + ")" );
471 }
472 CollectionFunction f = ( CollectionFunction ) rhs;
473 // Re-arrange the tree so that the collection function is the root and the lhs is the path.
474 f.setFirstChild( lhs );
475 lhs.setNextSibling( null );
476 dotNode.setFirstChild( f );
477 resolve( lhs ); // Don't forget to resolve the argument!
478 f.resolve( inSelect ); // Resolve the collection function now.
479 return f;
480 default:
481 // Resolve everything up to this dot, but don't resolve the placeholders yet.
482 dotNode.resolveFirstChild();
483 return dotNode;
484 }
485 }
486
487 protected boolean isNonQualifiedPropertyRef(AST ident) {
488 final String identText = ident.getText();
489 if ( currentFromClause.isFromElementAlias( identText ) ) {
490 return false;
491 }
492
493 List fromElements = currentFromClause.getExplicitFromElements();
494 if ( fromElements.size() == 1 ) {
495 final FromElement fromElement = ( FromElement ) fromElements.get( 0 );
496 try {
497 log.trace( "attempting to resolve property [" + identText + "] as a non-qualified ref" );
498 return fromElement.getPropertyMapping( identText ).toType( identText ) != null;
499 }
500 catch( QueryException e ) {
501 // Should mean that no such property was found
502 }
503 }
504
505 return false;
506 }
507
508 protected AST lookupNonQualifiedProperty(AST property) throws SemanticException {
509 final FromElement fromElement = ( FromElement ) currentFromClause.getExplicitFromElements().get( 0 );
510 AST syntheticDotNode = generateSyntheticDotNodeForNonQualifiedPropertyRef( property, fromElement );
511 return lookupProperty( syntheticDotNode, false, getCurrentClauseType() == HqlSqlTokenTypes.SELECT );
512 }
513
514 private AST generateSyntheticDotNodeForNonQualifiedPropertyRef(AST property, FromElement fromElement) {
515 AST dot = getASTFactory().create( DOT, "{non-qualified-property-ref}" );
516 // TODO : better way?!?
517 ( ( DotNode ) dot ).setPropertyPath( ( ( FromReferenceNode ) property ).getPath() );
518
519 IdentNode syntheticAlias = ( IdentNode ) getASTFactory().create( IDENT, "{synthetic-alias}" );
520 syntheticAlias.setFromElement( fromElement );
521 syntheticAlias.setResolved();
522
523 dot.setFirstChild( syntheticAlias );
524 dot.addChild( property );
525
526 return dot;
527 }
528
529 protected void processQuery(AST select, AST query) throws SemanticException {
530 if ( log.isDebugEnabled() ) {
531 log.debug( "processQuery() : " + query.toStringTree() );
532 }
533
534 try {
535 QueryNode qn = ( QueryNode ) query;
536
537 // Was there an explicit select expression?
538 boolean explicitSelect = select != null && select.getNumberOfChildren() > 0;
539
540 if ( !explicitSelect ) {
541 // No explicit select expression; render the id and properties
542 // projection lists for every persister in the from clause into
543 // a single 'token node'.
544 //TODO: the only reason we need this stuff now is collection filters,
545 // we should get rid of derived select clause completely!
546 createSelectClauseFromFromClause( qn );
547 }
548 else {
549 // Use the explicitly declared select expression; determine the
550 // return types indicated by each select token
551 useSelectClause( select );
552 }
553
554 // After that, process the JOINs.
555 // Invoke a delegate to do the work, as this is farily complex.
556 JoinProcessor joinProcessor = new JoinProcessor( astFactory, queryTranslatorImpl );
557 joinProcessor.processJoins( qn, isSubQuery() );
558
559 // Attach any mapping-defined "ORDER BY" fragments
560 Iterator itr = qn.getFromClause().getProjectionList().iterator();
561 while ( itr.hasNext() ) {
562 final FromElement fromElement = ( FromElement ) itr.next();
563 // if ( fromElement.isFetch() && fromElement.isCollectionJoin() ) {
564 if ( fromElement.isFetch() && fromElement.getQueryableCollection() != null ) {
565 // Does the collection referenced by this FromElement
566 // specify an order-by attribute? If so, attach it to
567 // the query's order-by
568 if ( fromElement.getQueryableCollection().hasOrdering() ) {
569 String orderByFragment = fromElement
570 .getQueryableCollection()
571 .getSQLOrderByString( fromElement.getCollectionTableAlias() );
572 qn.getOrderByClause().addOrderFragment( orderByFragment );
573 }
574 if ( fromElement.getQueryableCollection().hasManyToManyOrdering() ) {
575 String orderByFragment = fromElement.getQueryableCollection()
576 .getManyToManyOrderByString( fromElement.getTableAlias() );
577 qn.getOrderByClause().addOrderFragment( orderByFragment );
578 }
579 }
580 }
581 }
582 finally {
583 popFromClause();
584 }
585 }
586
587 protected void postProcessDML(RestrictableStatement statement) throws SemanticException {
588 statement.getFromClause().resolve();
589
590 FromElement fromElement = ( FromElement ) statement.getFromClause().getFromElements().get( 0 );
591 Queryable persister = fromElement.getQueryable();
592 // Make #@%$^#^&# sure no alias is applied to the table name
593 fromElement.setText( persister.getTableName() );
594
595 // append any filter fragments; the EMPTY_MAP is used under the assumption that
596 // currently enabled filters should not affect this process
597 if ( persister.getDiscriminatorType() != null ) {
598 new SyntheticAndFactory( getASTFactory() ).addDiscriminatorWhereFragment(
599 statement,
600 persister,
601 java.util.Collections.EMPTY_MAP,
602 fromElement.getTableAlias()
603 );
604 }
605
606 }
607
608 protected void postProcessUpdate(AST update) throws SemanticException {
609 UpdateStatement updateStatement = ( UpdateStatement ) update;
610
611 postProcessDML( updateStatement );
612 }
613
614 protected void postProcessDelete(AST delete) throws SemanticException {
615 postProcessDML( ( DeleteStatement ) delete );
616 }
617
618 public static boolean supportsIdGenWithBulkInsertion(IdentifierGenerator generator) {
619 return SequenceGenerator.class.isAssignableFrom( generator.getClass() )
620 || PostInsertIdentifierGenerator.class.isAssignableFrom( generator.getClass() );
621 }
622
623 protected void postProcessInsert(AST insert) throws SemanticException, QueryException {
624 InsertStatement insertStatement = ( InsertStatement ) insert;
625 insertStatement.validate();
626
627 SelectClause selectClause = insertStatement.getSelectClause();
628 Queryable persister = insertStatement.getIntoClause().getQueryable();
629
630 if ( !insertStatement.getIntoClause().isExplicitIdInsertion() ) {
631 // We need to generate ids as part of this bulk insert.
632 //
633 // Note that this is only supported for sequence-style generators and
634 // post-insert-style generators; basically, only in-db generators
635 IdentifierGenerator generator = persister.getIdentifierGenerator();
636 if ( !supportsIdGenWithBulkInsertion( generator ) ) {
637 throw new QueryException( "can only generate ids as part of bulk insert with either sequence or post-insert style generators" );
638 }
639
640 AST idSelectExprNode = null;
641
642 if ( SequenceGenerator.class.isAssignableFrom( generator.getClass() ) ) {
643 String seqName = ( String ) ( ( SequenceGenerator ) generator ).generatorKey();
644 String nextval = sessionFactoryHelper.getFactory().getDialect().getSelectSequenceNextValString( seqName );
645 idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, nextval );
646 }
647 else {
648 //Don't need this, because we should never ever be selecting no columns in an insert ... select...
649 //and because it causes a bug on DB2
650 /*String idInsertString = sessionFactoryHelper.getFactory().getDialect().getIdentityInsertString();
651 if ( idInsertString != null ) {
652 idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, idInsertString );
653 }*/
654 }
655
656 if ( idSelectExprNode != null ) {
657 AST currentFirstSelectExprNode = selectClause.getFirstChild();
658 selectClause.setFirstChild( idSelectExprNode );
659 idSelectExprNode.setNextSibling( currentFirstSelectExprNode );
660
661 insertStatement.getIntoClause().prependIdColumnSpec();
662 }
663 }
664
665 final boolean includeVersionProperty = persister.isVersioned() &&
666 !insertStatement.getIntoClause().isExplicitVersionInsertion() &&
667 persister.isVersionPropertyInsertable();
668 if ( includeVersionProperty ) {
669 // We need to seed the version value as part of this bulk insert
670 VersionType versionType = persister.getVersionType();
671 AST versionValueNode = null;
672
673 if ( sessionFactoryHelper.getFactory().getDialect().supportsParametersInInsertSelect() ) {
674 versionValueNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
675 ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
676 ( ( ParameterNode ) versionValueNode ).setHqlParameterSpecification( paramSpec );
677 parameters.add( 0, paramSpec );
678 }
679 else {
680 if ( isIntegral( versionType ) ) {
681 try {
682 Object seedValue = versionType.seed( null );
683 versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, seedValue.toString() );
684 }
685 catch( Throwable t ) {
686 throw new QueryException( "could not determine seed value for version on bulk insert [" + versionType + "]" );
687 }
688 }
689 else if ( isDatabaseGeneratedTimestamp( versionType ) ) {
690 String functionName = sessionFactoryHelper.getFactory().getDialect().getCurrentTimestampSQLFunctionName();
691 versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, functionName );
692 }
693 else {
694 throw new QueryException( "cannot handle version type [" + versionType + "] on bulk inserts with dialects not supporting parameters in insert-select statements" );
695 }
696 }
697
698 AST currentFirstSelectExprNode = selectClause.getFirstChild();
699 selectClause.setFirstChild( versionValueNode );
700 versionValueNode.setNextSibling( currentFirstSelectExprNode );
701
702 insertStatement.getIntoClause().prependVersionColumnSpec();
703 }
704
705 if ( insertStatement.getIntoClause().isDiscriminated() ) {
706 String sqlValue = insertStatement.getIntoClause().getQueryable().getDiscriminatorSQLValue();
707 AST discrimValue = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, sqlValue );
708 insertStatement.getSelectClause().addChild( discrimValue );
709 }
710
711 }
712
713 private boolean isDatabaseGeneratedTimestamp(Type type) {
714 // currently only the Hibernate-supplied DbTimestampType is supported here
715 return DbTimestampType.class.isAssignableFrom( type.getClass() );
716 }
717
718 private boolean isIntegral(Type type) {
719 return Long.class.isAssignableFrom( type.getReturnedClass() )
720 || Integer.class.isAssignableFrom( type.getReturnedClass() )
721 || long.class.isAssignableFrom( type.getReturnedClass() )
722 || int.class.isAssignableFrom( type.getReturnedClass() );
723 }
724
725 private void useSelectClause(AST select) throws SemanticException {
726 selectClause = ( SelectClause ) select;
727 selectClause.initializeExplicitSelectClause( currentFromClause );
728 }
729
730 private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticException {
731 AST select = astFactory.create( SELECT_CLAUSE, "{derived select clause}" );
732 AST sibling = qn.getFromClause();
733 qn.setFirstChild( select );
734 select.setNextSibling( sibling );
735 selectClause = ( SelectClause ) select;
736 selectClause.initializeDerivedSelectClause( currentFromClause );
737 if ( log.isDebugEnabled() ) {
738 log.debug( "Derived SELECT clause created." );
739 }
740 }
741
742 protected void resolve(AST node) throws SemanticException {
743 if ( node != null ) {
744 // This is called when it's time to fully resolve a path expression.
745 ResolvableNode r = ( ResolvableNode ) node;
746 if ( isInFunctionCall() ) {
747 r.resolveInFunctionCall( false, true );
748 }
749 else {
750 r.resolve( false, true ); // Generate implicit joins, only if necessary.
751 }
752 }
753 }
754
755 protected void resolveSelectExpression(AST node) throws SemanticException {
756 // This is called when it's time to fully resolve a path expression.
757 int type = node.getType();
758 switch ( type ) {
759 case DOT:
760 DotNode dot = ( DotNode ) node;
761 dot.resolveSelectExpression();
762 break;
763 case ALIAS_REF:
764 // Notify the FROM element that it is being referenced by the select.
765 FromReferenceNode aliasRefNode = ( FromReferenceNode ) node;
766 //aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here?
767 aliasRefNode.resolve( false, false ); //TODO: is it kosher to do it here?
768 FromElement fromElement = aliasRefNode.getFromElement();
769 if ( fromElement != null ) {
770 fromElement.setIncludeSubclasses( true );
771 }
772 default:
773 break;
774 }
775 }
776
777 protected void beforeSelectClause() throws SemanticException {
778 // Turn off includeSubclasses on all FromElements.
779 FromClause from = getCurrentFromClause();
780 List fromElements = from.getFromElements();
781 for ( Iterator iterator = fromElements.iterator(); iterator.hasNext(); ) {
782 FromElement fromElement = ( FromElement ) iterator.next();
783 fromElement.setIncludeSubclasses( false );
784 }
785 }
786
787 protected AST generatePositionalParameter(AST inputNode) throws SemanticException {
788 if ( namedParameters.size() > 0 ) {
789 throw new SemanticException( "cannot define positional parameter after any named parameters have been defined" );
790 }
791 ParameterNode parameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
792 PositionalParameterSpecification paramSpec = new PositionalParameterSpecification(
793 ( ( Node ) inputNode ).getLine(),
794 ( ( Node ) inputNode ).getColumn(),
795 positionalParameterCount++
796 );
797 parameter.setHqlParameterSpecification( paramSpec );
798 parameters.add( paramSpec );
799 return parameter;
800 }
801
802 protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {
803 String name = nameNode.getText();
804 trackNamedParameterPositions( name );
805
806 // create the node initially with the param name so that it shows
807 // appropriately in the "original text" attribute
808 ParameterNode parameter = ( ParameterNode ) astFactory.create( NAMED_PARAM, name );
809 parameter.setText( "?" );
810
811 NamedParameterSpecification paramSpec = new NamedParameterSpecification(
812 ( ( Node ) delimiterNode ).getLine(),
813 ( ( Node ) delimiterNode ).getColumn(),
814 name
815 );
816 parameter.setHqlParameterSpecification( paramSpec );
817 parameters.add( paramSpec );
818 return parameter;
819 }
820
821 private void trackNamedParameterPositions(String name) {
822 Integer loc = new Integer( parameterCount++ );
823 Object o = namedParameters.get( name );
824 if ( o == null ) {
825 namedParameters.put( name, loc );
826 }
827 else if ( o instanceof Integer ) {
828 ArrayList list = new ArrayList( 4 );
829 list.add( o );
830 list.add( loc );
831 namedParameters.put( name, list );
832 }
833 else {
834 ( ( ArrayList ) o ).add( loc );
835 }
836 }
837
838 protected void processConstant(AST constant) throws SemanticException {
839 literalProcessor.processConstant( constant, true ); // Use the delegate, resolve identifiers as FROM element aliases.
840 }
841
842 protected void processBoolean(AST constant) throws SemanticException {
843 literalProcessor.processBoolean( constant ); // Use the delegate.
844 }
845
846 protected void processNumericLiteral(AST literal) {
847 literalProcessor.processNumeric( literal );
848 }
849
850 protected void processIndex(AST indexOp) throws SemanticException {
851 IndexNode indexNode = ( IndexNode ) indexOp;
852 indexNode.resolve( true, true );
853 }
854
855 protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
856 MethodNode methodNode = ( MethodNode ) functionCall;
857 methodNode.resolve( inSelect );
858 }
859
860 protected void processConstructor(AST constructor) throws SemanticException {
861 ConstructorNode constructorNode = ( ConstructorNode ) constructor;
862 constructorNode.prepare();
863 }
864
865 protected void setAlias(AST selectExpr, AST ident) {
866 ((SelectExpression) selectExpr).setAlias(ident.getText());
867 }
868
869 /**
870 * Returns the locations of all occurrences of the named parameter.
871 */
872 public int[] getNamedParameterLocations(String name) throws QueryException {
873 Object o = namedParameters.get( name );
874 if ( o == null ) {
875 QueryException qe = new QueryException( QueryTranslator.ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR + name );
876 qe.setQueryString( queryTranslatorImpl.getQueryString() );
877 throw qe;
878 }
879 if ( o instanceof Integer ) {
880 return new int[]{( ( Integer ) o ).intValue()};
881 }
882 else {
883 return ArrayHelper.toIntArray( ( ArrayList ) o );
884 }
885 }
886
887 public void addQuerySpaces(Serializable[] spaces) {
888 for ( int i = 0; i < spaces.length; i++ ) {
889 querySpaces.add( spaces[i] );
890 }
891 }
892
893 public Type[] getReturnTypes() {
894 return selectClause.getQueryReturnTypes();
895 }
896
897 public String[] getReturnAliases() {
898 return selectClause.getQueryReturnAliases();
899 }
900
901 public SelectClause getSelectClause() {
902 return selectClause;
903 }
904
905 public FromClause getFinalFromClause() {
906 FromClause top = currentFromClause;
907 while ( top.getParentFromClause() != null ) {
908 top = top.getParentFromClause();
909 }
910 return top;
911 }
912
913 public boolean isShallowQuery() {
914 // select clauses for insert statements should alwasy be treated as shallow
915 return getStatementType() == INSERT || queryTranslatorImpl.isShallowQuery();
916 }
917
918 public Map getEnabledFilters() {
919 return queryTranslatorImpl.getEnabledFilters();
920 }
921
922 public LiteralProcessor getLiteralProcessor() {
923 return literalProcessor;
924 }
925
926 public ASTPrinter getASTPrinter() {
927 return printer;
928 }
929
930 public ArrayList getParameters() {
931 return parameters;
932 }
933
934 public int getNumberOfParametersInSetClause() {
935 return numberOfParametersInSetClause;
936 }
937
938 protected void evaluateAssignment(AST eq) throws SemanticException {
939 prepareLogicOperator( eq );
940 Queryable persister = getCurrentFromClause().getFromElement().getQueryable();
941 evaluateAssignment( eq, persister, -1 );
942 }
943
944 private void evaluateAssignment(AST eq, Queryable persister, int targetIndex) {
945 if ( persister.isMultiTable() ) {
946 // no need to even collect this information if the persister is considered multi-table
947 AssignmentSpecification specification = new AssignmentSpecification( eq, persister );
948 if ( targetIndex >= 0 ) {
949 assignmentSpecifications.add( targetIndex, specification );
950 }
951 else {
952 assignmentSpecifications.add( specification );
953 }
954 numberOfParametersInSetClause += specification.getParameters().length;
955 }
956 }
957
958 public ArrayList getAssignmentSpecifications() {
959 return assignmentSpecifications;
960 }
961
962 protected AST createIntoClause(String path, AST propertySpec) throws SemanticException {
963 Queryable persister = ( Queryable ) getSessionFactoryHelper().requireClassPersister( path );
964
965 IntoClause intoClause = ( IntoClause ) getASTFactory().create( INTO, persister.getEntityName() );
966 intoClause.setFirstChild( propertySpec );
967 intoClause.initialize( persister );
968
969 addQuerySpaces( persister.getQuerySpaces() );
970
971 return intoClause;
972 }
973
974 protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticException {
975 UpdateStatement updateStatement = ( UpdateStatement ) updateNode;
976 FromClause fromClause = updateStatement.getFromClause();
977 if ( versioned != null ) {
978 // Make sure that the persister is versioned
979 Queryable persister = fromClause.getFromElement().getQueryable();
980 if ( !persister.isVersioned() ) {
981 throw new SemanticException( "increment option specified for update of non-versioned entity" );
982 }
983
984 VersionType versionType = persister.getVersionType();
985 if ( versionType instanceof UserVersionType ) {
986 throw new SemanticException( "user-defined version types not supported for increment option" );
987 }
988
989 AST eq = getASTFactory().create( HqlSqlTokenTypes.EQ, "=" );
990 AST versionPropertyNode = generateVersionPropertyNode( persister );
991
992 eq.setFirstChild( versionPropertyNode );
993
994 AST versionIncrementNode = null;
995 if ( Date.class.isAssignableFrom( versionType.getReturnedClass() ) ) {
996 versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
997 ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
998 ( ( ParameterNode ) versionIncrementNode ).setHqlParameterSpecification( paramSpec );
999 parameters.add( 0, paramSpec );
1000 }
1001 else {
1002 // Not possible to simply re-use the versionPropertyNode here as it causes
1003 // OOM errors due to circularity :(
1004 versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PLUS, "+" );
1005 versionIncrementNode.setFirstChild( generateVersionPropertyNode( persister ) );
1006 versionIncrementNode.addChild( getASTFactory().create( HqlSqlTokenTypes.IDENT, "1" ) );
1007 }
1008
1009 eq.addChild( versionIncrementNode );
1010
1011 evaluateAssignment( eq, persister, 0 );
1012
1013 AST setClause = updateStatement.getSetClause();
1014 AST currentFirstSetElement = setClause.getFirstChild();
1015 setClause.setFirstChild( eq );
1016 eq.setNextSibling( currentFirstSetElement );
1017 }
1018 }
1019
1020 private AST generateVersionPropertyNode(Queryable persister) throws SemanticException {
1021 String versionPropertyName = persister.getPropertyNames()[ persister.getVersionProperty() ];
1022 AST versionPropertyRef = getASTFactory().create( HqlSqlTokenTypes.IDENT, versionPropertyName );
1023 AST versionPropertyNode = lookupNonQualifiedProperty( versionPropertyRef );
1024 resolve( versionPropertyNode );
1025 return versionPropertyNode;
1026 }
1027
1028 protected void prepareLogicOperator(AST operator) throws SemanticException {
1029 ( ( OperatorNode ) operator ).initialize();
1030 }
1031
1032 protected void prepareArithmeticOperator(AST operator) throws SemanticException {
1033 ( ( OperatorNode ) operator ).initialize();
1034 }
1035
1036 public static void panic() {
1037 throw new QueryException( "TreeWalker: panic" );
1038 }
1039 }