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