/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Middleware LLC. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA * */ package org.hibernate.hql.internal.ast; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.QueryException; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.engine.internal.ParameterBinder; import org.hibernate.hql.internal.antlr.HqlSqlBaseWalker; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.antlr.HqlTokenTypes; import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.tree.AggregateNode; import org.hibernate.hql.internal.ast.tree.AssignmentSpecification; import org.hibernate.hql.internal.ast.tree.CollectionFunction; import org.hibernate.hql.internal.ast.tree.ConstructorNode; import org.hibernate.hql.internal.ast.tree.DeleteStatement; import org.hibernate.hql.internal.ast.tree.DotNode; import org.hibernate.hql.internal.ast.tree.FromClause; import org.hibernate.hql.internal.ast.tree.FromElement; import org.hibernate.hql.internal.ast.tree.FromElementFactory; import org.hibernate.hql.internal.ast.tree.FromReferenceNode; import org.hibernate.hql.internal.ast.tree.IdentNode; import org.hibernate.hql.internal.ast.tree.IndexNode; import org.hibernate.hql.internal.ast.tree.InsertStatement; import org.hibernate.hql.internal.ast.tree.IntoClause; import org.hibernate.hql.internal.ast.tree.MethodNode; import org.hibernate.hql.internal.ast.tree.OperatorNode; import org.hibernate.hql.internal.ast.tree.ParameterContainer; import org.hibernate.hql.internal.ast.tree.ParameterNode; import org.hibernate.hql.internal.ast.tree.QueryNode; import org.hibernate.hql.internal.ast.tree.ResolvableNode; import org.hibernate.hql.internal.ast.tree.RestrictableStatement; import org.hibernate.hql.internal.ast.tree.ResultVariableRefNode; import org.hibernate.hql.internal.ast.tree.SelectClause; import org.hibernate.hql.internal.ast.tree.SelectExpression; import org.hibernate.hql.internal.ast.tree.UpdateStatement; import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.hql.internal.ast.util.ASTUtil; import org.hibernate.hql.internal.ast.util.AliasGenerator; import org.hibernate.hql.internal.ast.util.JoinProcessor; import org.hibernate.hql.internal.ast.util.LiteralProcessor; import org.hibernate.hql.internal.ast.util.NodeTraverser; import org.hibernate.hql.internal.ast.util.SessionFactoryHelper; import org.hibernate.hql.internal.ast.util.SyntheticAndFactory; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.SequenceGenerator; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.param.CollectionFilterKeyParameterSpecification; import org.hibernate.param.NamedParameterSpecification; import org.hibernate.param.ParameterSpecification; import org.hibernate.param.PositionalParameterSpecification; import org.hibernate.param.VersionTypeSeedParameterSpecification; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.Queryable; import org.hibernate.sql.JoinType; import org.hibernate.type.AssociationType; import org.hibernate.type.ComponentType; import org.hibernate.type.DbTimestampType; import org.hibernate.type.Type; import org.hibernate.type.VersionType; import org.hibernate.usertype.UserVersionType; import org.jboss.logging.Logger; import antlr.ASTFactory; import antlr.RecognitionException; import antlr.SemanticException; import antlr.collections.AST; /** * Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase). * <ul> * <li>Isolates the Hibernate API-specific code from the ANTLR generated code.</li> * <li>Handles the SQL fragments generated by the persisters in order to create the SELECT and FROM clauses, * taking into account the joins and projections that are implied by the mappings (persister/queryable).</li> * <li>Uses SqlASTFactory to create customized AST nodes.</li> * </ul> * * @see SqlASTFactory */ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, ParameterBinder.NamedParameterSource { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, HqlSqlWalker.class.getName()); private final QueryTranslatorImpl queryTranslatorImpl; private final HqlParser hqlParser; private final SessionFactoryHelper sessionFactoryHelper; private final Map tokenReplacements; private final AliasGenerator aliasGenerator = new AliasGenerator(); private final LiteralProcessor literalProcessor; private final ParseErrorHandler parseErrorHandler; private final ASTPrinter printer; private final String collectionFilterRole; private FromClause currentFromClause = null; private SelectClause selectClause; /** * Maps each top-level result variable to its SelectExpression; * (excludes result variables defined in subqueries) **/ private Map<String, SelectExpression> selectExpressionsByResultVariable = new HashMap(); private Set querySpaces = new HashSet(); private int parameterCount; private Map namedParameters = new HashMap(); private ArrayList parameters = new ArrayList(); private int numberOfParametersInSetClause; private int positionalParameterCount; private ArrayList assignmentSpecifications = new ArrayList(); private JoinType impliedJoinType = JoinType.INNER_JOIN; /** * Create a new tree transformer. * * @param qti Back pointer to the query translator implementation that is using this tree transform. * @param sfi The session factory implementor where the Hibernate mappings can be found. * @param parser A reference to the phase-1 parser * @param tokenReplacements Registers the token replacement map with the walker. This map will * be used to substitute function names and constants. * @param collectionRole The collection role name of the collection used as the basis for the * filter, NULL if this is not a collection filter compilation. */ public HqlSqlWalker( QueryTranslatorImpl qti, SessionFactoryImplementor sfi, HqlParser parser, Map tokenReplacements, String collectionRole) { setASTFactory( new SqlASTFactory( this ) ); // Initialize the error handling delegate. this.parseErrorHandler = new ErrorCounter(); this.queryTranslatorImpl = qti; this.sessionFactoryHelper = new SessionFactoryHelper( sfi ); this.literalProcessor = new LiteralProcessor( this ); this.tokenReplacements = tokenReplacements; this.collectionFilterRole = collectionRole; this.hqlParser = parser; this.printer = new ASTPrinter( SqlTokenTypes.class ); } // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private int traceDepth = 0; @Override public void traceIn(String ruleName, AST tree) { if (!LOG.isTraceEnabled()) return; if (inputState.guessing > 0) return; String prefix = StringHelper.repeat( '-', (traceDepth++ * 2) ) + "-> "; String traceText = ruleName + " (" + buildTraceNodeName(tree) + ")"; LOG.trace(prefix + traceText); } private String buildTraceNodeName(AST tree) { return tree == null ? "???" : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; } @Override public void traceOut(String ruleName, AST tree) { if (!LOG.isTraceEnabled()) return; if (inputState.guessing > 0) return; String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; LOG.trace(prefix + ruleName); } @Override protected void prepareFromClauseInputTree(AST fromClauseInput) { if ( !isSubQuery() ) { // // inject param specifications to account for dynamic filter param values // if ( ! getEnabledFilters().isEmpty() ) { // Iterator filterItr = getEnabledFilters().values().iterator(); // while ( filterItr.hasNext() ) { // FilterImpl filter = ( FilterImpl ) filterItr.next(); // if ( ! filter.getFilterDefinition().getParameterNames().isEmpty() ) { // Iterator paramItr = filter.getFilterDefinition().getParameterNames().iterator(); // while ( paramItr.hasNext() ) { // String parameterName = ( String ) paramItr.next(); // // currently param filters *only* work with single-column parameter types; // // if that limitation is ever lifted, this logic will need to change to account for that // ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" ); // DynamicFilterParameterSpecification paramSpec = new DynamicFilterParameterSpecification( // filter.getName(), // parameterName, // filter.getFilterDefinition().getParameterType( parameterName ), // positionalParameterCount++ // ); // collectionFilterKeyParameter.setHqlParameterSpecification( paramSpec ); // parameters.add( paramSpec ); // } // } // } // } if ( isFilter() ) { // Handle collection-filter compilation. // IMPORTANT NOTE: This is modifying the INPUT (HQL) tree, not the output tree! QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole ); Type collectionElementType = persister.getElementType(); if ( !collectionElementType.isEntityType() ) { throw new QueryException( "collection of values in filter: this" ); } String collectionElementEntityName = persister.getElementPersister().getEntityName(); ASTFactory inputAstFactory = hqlParser.getASTFactory(); AST fromElement = ASTUtil.create( inputAstFactory, HqlTokenTypes.FILTER_ENTITY, collectionElementEntityName ); ASTUtil.createSibling( inputAstFactory, HqlTokenTypes.ALIAS, "this", fromElement ); fromClauseInput.addChild( fromElement ); // Show the modified AST. LOG.debugf("prepareFromClauseInputTree() : Filter - Added 'this' as a from element..."); queryTranslatorImpl.showHqlAst( hqlParser.getAST() ); // Create a parameter specification for the collection filter... Type collectionFilterKeyType = sessionFactoryHelper.requireQueryableCollection( collectionFilterRole ).getKeyType(); ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" ); CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification( collectionFilterRole, collectionFilterKeyType, positionalParameterCount++ ); collectionFilterKeyParameter.setHqlParameterSpecification( collectionFilterKeyParameterSpec ); parameters.add( collectionFilterKeyParameterSpec ); } } } public boolean isFilter() { return collectionFilterRole != null; } public String getCollectionFilterRole() { return collectionFilterRole; } public SessionFactoryHelper getSessionFactoryHelper() { return sessionFactoryHelper; } public Map getTokenReplacements() { return tokenReplacements; } public AliasGenerator getAliasGenerator() { return aliasGenerator; } public FromClause getCurrentFromClause() { return currentFromClause; } public ParseErrorHandler getParseErrorHandler() { return parseErrorHandler; } @Override public void reportError(RecognitionException e) { parseErrorHandler.reportError( e ); // Use the delegate. } @Override public void reportError(String s) { parseErrorHandler.reportError( s ); // Use the delegate. } @Override public void reportWarning(String s) { parseErrorHandler.reportWarning( s ); } /** * Returns the set of unique query spaces (a.k.a. * table names) that occurred in the query. * * @return A set of table names (Strings). */ public Set getQuerySpaces() { return querySpaces; } @Override protected AST createFromElement(String path, AST alias, AST propertyFetch) throws SemanticException { FromElement fromElement = currentFromClause.addFromElement( path, alias ); fromElement.setAllPropertyFetch(propertyFetch!=null); return fromElement; } @Override protected AST createFromFilterElement(AST filterEntity, AST alias) throws SemanticException { FromElement fromElement = currentFromClause.addFromElement( filterEntity.getText(), alias ); FromClause fromClause = fromElement.getFromClause(); QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole ); // Get the names of the columns used to link between the collection // owner and the collection elements. String[] keyColumnNames = persister.getKeyColumnNames(); String fkTableAlias = persister.isOneToMany() ? fromElement.getTableAlias() : fromClause.getAliasGenerator().createName( collectionFilterRole ); JoinSequence join = sessionFactoryHelper.createJoinSequence(); join.setRoot( persister, fkTableAlias ); if ( !persister.isOneToMany() ) { join.addJoin( ( AssociationType ) persister.getElementType(), fromElement.getTableAlias(), JoinType.INNER_JOIN, persister.getElementColumnNames( fkTableAlias ) ); } join.addCondition( fkTableAlias, keyColumnNames, " = ?" ); fromElement.setJoinSequence( join ); fromElement.setFilter( true ); LOG.debugf("createFromFilterElement() : processed filter FROM element."); return fromElement; } @Override protected void createFromJoinElement( AST path, AST alias, int joinType, AST fetchNode, AST propertyFetch, AST with) throws SemanticException { boolean fetch = fetchNode != null; if ( fetch && isSubQuery() ) { throw new QueryException( "fetch not allowed in subquery from-elements" ); } // The path AST should be a DotNode, and it should have been evaluated already. if ( path.getType() != SqlTokenTypes.DOT ) { throw new SemanticException( "Path expected for join!" ); } DotNode dot = ( DotNode ) path; JoinType hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType ); dot.setJoinType( hibernateJoinType ); // Tell the dot node about the join type. dot.setFetch( fetch ); // Generate an explicit join for the root dot node. The implied joins will be collected and passed up // to the root dot node. dot.resolve( true, false, alias == null ? null : alias.getText() ); final FromElement fromElement; if ( dot.getDataType() != null && dot.getDataType().isComponentType() ) { FromElementFactory factory = new FromElementFactory( getCurrentFromClause(), dot.getLhs().getFromElement(), dot.getPropertyPath(), alias == null ? null : alias.getText(), null, false ); fromElement = factory.createComponentJoin( (ComponentType) dot.getDataType() ); } else { fromElement = dot.getImpliedJoin(); fromElement.setAllPropertyFetch( propertyFetch != null ); if ( with != null ) { if ( fetch ) { throw new SemanticException( "with-clause not allowed on fetched associations; use filters" ); } handleWithFragment( fromElement, with ); } } if (LOG.isDebugEnabled()) LOG.debugf("createFromJoinElement() : %s", getASTPrinter().showAsString(fromElement, "-- join tree --")); } private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException { try { withClause( hqlWithNode ); AST hqlSqlWithNode = returnAST; if (LOG.isDebugEnabled()) LOG.debugf("handleWithFragment() : %s", getASTPrinter().showAsString(hqlSqlWithNode, "-- with clause --")); WithClauseVisitor visitor = new WithClauseVisitor( fromElement ); NodeTraverser traverser = new NodeTraverser( visitor ); traverser.traverseDepthFirst( hqlSqlWithNode ); String withClauseJoinAlias = visitor.getJoinAlias(); if ( withClauseJoinAlias == null ) { withClauseJoinAlias = fromElement.getCollectionTableAlias(); } else { FromElement referencedFromElement = visitor.getReferencedFromElement(); if ( referencedFromElement != fromElement ) { throw new InvalidWithClauseException( "with-clause expressions did not reference from-clause element to which the with-clause was associated" ); } } SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() ); sql.whereExpr( hqlSqlWithNode.getFirstChild() ); fromElement.setWithClauseFragment( withClauseJoinAlias, "(" + sql.getSQL() + ")" ); } catch( SemanticException e ) { throw e; } catch( InvalidWithClauseException e ) { throw e; } catch ( Exception e) { throw new SemanticException( e.getMessage() ); } } private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy { private final FromElement joinFragment; private FromElement referencedFromElement; private String joinAlias; public WithClauseVisitor(FromElement fromElement) { this.joinFragment = fromElement; } public void visit(AST node) { // TODO : currently expects that the individual with expressions apply to the same sql table join. // This may not be the case for joined-subclass where the property values // might be coming from different tables in the joined hierarchy. At some // point we should expand this to support that capability. However, that has // some difficulties: // 1) the biggest is how to handle ORs when the individual comparisons are // linked to different sql joins. // 2) here we would need to track each comparison individually, along with // the join alias to which it applies and then pass that information // back to the FromElement so it can pass it along to the JoinSequence if ( node instanceof DotNode ) { DotNode dotNode = ( DotNode ) node; FromElement fromElement = dotNode.getFromElement(); if ( referencedFromElement != null ) { if ( fromElement != referencedFromElement ) { throw new HibernateException( "with-clause referenced two different from-clause elements" ); } } else { referencedFromElement = fromElement; joinAlias = extractAppliedAlias( dotNode ); // TODO : temporary // needed because currently persister is the one that // creates and renders the join fragments for inheritance // hierarchies... if ( !joinAlias.equals( referencedFromElement.getTableAlias() ) ) { throw new InvalidWithClauseException( "with clause can only reference columns in the driving table" ); } } } else if ( node instanceof ParameterNode ) { applyParameterSpecification( ( ( ParameterNode ) node ).getHqlParameterSpecification() ); } else if ( node instanceof ParameterContainer ) { applyParameterSpecifications( ( ParameterContainer ) node ); } } private void applyParameterSpecifications(ParameterContainer parameterContainer) { if ( parameterContainer.hasEmbeddedParameters() ) { ParameterSpecification[] specs = parameterContainer.getEmbeddedParameters(); for ( int i = 0; i < specs.length; i++ ) { applyParameterSpecification( specs[i] ); } } } private void applyParameterSpecification(ParameterSpecification paramSpec) { joinFragment.addEmbeddedParameter( paramSpec ); } private String extractAppliedAlias(DotNode dotNode) { return dotNode.getText().substring( 0, dotNode.getText().indexOf( '.' ) ); } public FromElement getReferencedFromElement() { return referencedFromElement; } public String getJoinAlias() { return joinAlias; } } /** * Sets the current 'FROM' context. * * @param fromNode The new 'FROM' context. * @param inputFromNode The from node from the input AST. */ @Override protected void pushFromClause(AST fromNode, AST inputFromNode) { FromClause newFromClause = ( FromClause ) fromNode; newFromClause.setParentFromClause( currentFromClause ); currentFromClause = newFromClause; } /** * Returns to the previous 'FROM' context. */ private void popFromClause() { currentFromClause = currentFromClause.getParentFromClause(); } @Override protected void lookupAlias(AST aliasRef) throws SemanticException { FromElement alias = currentFromClause.getFromElement( aliasRef.getText() ); FromReferenceNode aliasRefNode = ( FromReferenceNode ) aliasRef; aliasRefNode.setFromElement( alias ); } @Override protected void setImpliedJoinType(int joinType) { impliedJoinType = JoinProcessor.toHibernateJoinType( joinType ); } public JoinType getImpliedJoinType() { return impliedJoinType; } @Override protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException { DotNode dotNode = ( DotNode ) dot; FromReferenceNode lhs = dotNode.getLhs(); AST rhs = lhs.getNextSibling(); switch ( rhs.getType() ) { case SqlTokenTypes.ELEMENTS: case SqlTokenTypes.INDICES: if (LOG.isDebugEnabled()) LOG.debugf("lookupProperty() %s => %s(%s)", dotNode.getPath(), rhs.getText(), lhs.getPath()); CollectionFunction f = ( CollectionFunction ) rhs; // Re-arrange the tree so that the collection function is the root and the lhs is the path. f.setFirstChild( lhs ); lhs.setNextSibling( null ); dotNode.setFirstChild( f ); resolve( lhs ); // Don't forget to resolve the argument! f.resolve( inSelect ); // Resolve the collection function now. return f; default: // Resolve everything up to this dot, but don't resolve the placeholders yet. dotNode.resolveFirstChild(); return dotNode; } } @Override protected boolean isNonQualifiedPropertyRef(AST ident) { final String identText = ident.getText(); if ( currentFromClause.isFromElementAlias( identText ) ) { return false; } List fromElements = currentFromClause.getExplicitFromElements(); if ( fromElements.size() == 1 ) { final FromElement fromElement = ( FromElement ) fromElements.get( 0 ); try { LOG.trace("Attempting to resolve property [" + identText + "] as a non-qualified ref"); return fromElement.getPropertyMapping( identText ).toType( identText ) != null; } catch( QueryException e ) { // Should mean that no such property was found } } return false; } @Override protected AST lookupNonQualifiedProperty(AST property) throws SemanticException { final FromElement fromElement = ( FromElement ) currentFromClause.getExplicitFromElements().get( 0 ); AST syntheticDotNode = generateSyntheticDotNodeForNonQualifiedPropertyRef( property, fromElement ); return lookupProperty( syntheticDotNode, false, getCurrentClauseType() == HqlSqlTokenTypes.SELECT ); } private AST generateSyntheticDotNodeForNonQualifiedPropertyRef(AST property, FromElement fromElement) { AST dot = getASTFactory().create( DOT, "{non-qualified-property-ref}" ); // TODO : better way?!? ( ( DotNode ) dot ).setPropertyPath( ( ( FromReferenceNode ) property ).getPath() ); IdentNode syntheticAlias = ( IdentNode ) getASTFactory().create( IDENT, "{synthetic-alias}" ); syntheticAlias.setFromElement( fromElement ); syntheticAlias.setResolved(); dot.setFirstChild( syntheticAlias ); dot.addChild( property ); return dot; } @Override protected void processQuery(AST select, AST query) throws SemanticException { LOG.debugf("processQuery() : %s", query.toStringTree()); try { QueryNode qn = ( QueryNode ) query; // Was there an explicit select expression? boolean explicitSelect = select != null && select.getNumberOfChildren() > 0; if ( !explicitSelect ) { // No explicit select expression; render the id and properties // projection lists for every persister in the from clause into // a single 'token node'. //TODO: the only reason we need this stuff now is collection filters, // we should get rid of derived select clause completely! createSelectClauseFromFromClause( qn ); } else { // Use the explicitly declared select expression; determine the // return types indicated by each select token useSelectClause( select ); } // After that, process the JOINs. // Invoke a delegate to do the work, as this is farily complex. JoinProcessor joinProcessor = new JoinProcessor( this ); joinProcessor.processJoins( qn ); // Attach any mapping-defined "ORDER BY" fragments Iterator itr = qn.getFromClause().getProjectionList().iterator(); while ( itr.hasNext() ) { final FromElement fromElement = ( FromElement ) itr.next(); // if ( fromElement.isFetch() && fromElement.isCollectionJoin() ) { if ( fromElement.isFetch() && fromElement.getQueryableCollection() != null ) { // Does the collection referenced by this FromElement // specify an order-by attribute? If so, attach it to // the query's order-by if ( fromElement.getQueryableCollection().hasOrdering() ) { String orderByFragment = fromElement .getQueryableCollection() .getSQLOrderByString( fromElement.getCollectionTableAlias() ); qn.getOrderByClause().addOrderFragment( orderByFragment ); } if ( fromElement.getQueryableCollection().hasManyToManyOrdering() ) { String orderByFragment = fromElement.getQueryableCollection() .getManyToManyOrderByString( fromElement.getTableAlias() ); qn.getOrderByClause().addOrderFragment( orderByFragment ); } } } } finally { popFromClause(); } } protected void postProcessDML(RestrictableStatement statement) throws SemanticException { statement.getFromClause().resolve(); FromElement fromElement = ( FromElement ) statement.getFromClause().getFromElements().get( 0 ); Queryable persister = fromElement.getQueryable(); // Make #@%$^#^&# sure no alias is applied to the table name fromElement.setText( persister.getTableName() ); // // append any filter fragments; the EMPTY_MAP is used under the assumption that // // currently enabled filters should not affect this process // if ( persister.getDiscriminatorType() != null ) { // new SyntheticAndFactory( getASTFactory() ).addDiscriminatorWhereFragment( // statement, // persister, // java.util.Collections.EMPTY_MAP, // fromElement.getTableAlias() // ); // } if ( persister.getDiscriminatorType() != null || ! queryTranslatorImpl.getEnabledFilters().isEmpty() ) { new SyntheticAndFactory( this ).addDiscriminatorWhereFragment( statement, persister, queryTranslatorImpl.getEnabledFilters(), fromElement.getTableAlias() ); } } @Override protected void postProcessUpdate(AST update) throws SemanticException { UpdateStatement updateStatement = ( UpdateStatement ) update; postProcessDML( updateStatement ); } @Override protected void postProcessDelete(AST delete) throws SemanticException { postProcessDML( ( DeleteStatement ) delete ); } public static boolean supportsIdGenWithBulkInsertion(IdentifierGenerator generator) { return SequenceGenerator.class.isAssignableFrom( generator.getClass() ) || PostInsertIdentifierGenerator.class.isAssignableFrom( generator.getClass() ); } @Override protected void postProcessInsert(AST insert) throws SemanticException, QueryException { InsertStatement insertStatement = ( InsertStatement ) insert; insertStatement.validate(); SelectClause selectClause = insertStatement.getSelectClause(); Queryable persister = insertStatement.getIntoClause().getQueryable(); if ( !insertStatement.getIntoClause().isExplicitIdInsertion() ) { // We need to generate ids as part of this bulk insert. // // Note that this is only supported for sequence-style generators and // post-insert-style generators; basically, only in-db generators IdentifierGenerator generator = persister.getIdentifierGenerator(); if ( !supportsIdGenWithBulkInsertion( generator ) ) { throw new QueryException( "can only generate ids as part of bulk insert with either sequence or post-insert style generators" ); } AST idSelectExprNode = null; if ( SequenceGenerator.class.isAssignableFrom( generator.getClass() ) ) { String seqName = ( String ) ( ( SequenceGenerator ) generator ).generatorKey(); String nextval = sessionFactoryHelper.getFactory().getDialect().getSelectSequenceNextValString( seqName ); idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, nextval ); } else { //Don't need this, because we should never ever be selecting no columns in an insert ... select... //and because it causes a bug on DB2 /*String idInsertString = sessionFactoryHelper.getFactory().getDialect().getIdentityInsertString(); if ( idInsertString != null ) { idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, idInsertString ); }*/ } if ( idSelectExprNode != null ) { AST currentFirstSelectExprNode = selectClause.getFirstChild(); selectClause.setFirstChild( idSelectExprNode ); idSelectExprNode.setNextSibling( currentFirstSelectExprNode ); insertStatement.getIntoClause().prependIdColumnSpec(); } } final boolean includeVersionProperty = persister.isVersioned() && !insertStatement.getIntoClause().isExplicitVersionInsertion() && persister.isVersionPropertyInsertable(); if ( includeVersionProperty ) { // We need to seed the version value as part of this bulk insert VersionType versionType = persister.getVersionType(); AST versionValueNode = null; if ( sessionFactoryHelper.getFactory().getDialect().supportsParametersInInsertSelect() ) { int sqlTypes[] = versionType.sqlTypes( sessionFactoryHelper.getFactory() ); if ( sqlTypes == null || sqlTypes.length == 0 ) { throw new IllegalStateException( versionType.getClass() + ".sqlTypes() returns null or empty array" ); } if ( sqlTypes.length > 1 ) { throw new IllegalStateException( versionType.getClass() + ".sqlTypes() returns > 1 element; only single-valued versions are allowed." ); } versionValueNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" ); ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType ); ( ( ParameterNode ) versionValueNode ).setHqlParameterSpecification( paramSpec ); parameters.add( 0, paramSpec ); if ( sessionFactoryHelper.getFactory().getDialect().requiresCastingOfParametersInSelectClause() ) { // we need to wrtap the param in a cast() MethodNode versionMethodNode = ( MethodNode ) getASTFactory().create( HqlSqlTokenTypes.METHOD_CALL, "(" ); AST methodIdentNode = getASTFactory().create( HqlSqlTokenTypes.IDENT, "cast" ); versionMethodNode.addChild( methodIdentNode ); versionMethodNode.initializeMethodNode(methodIdentNode, true ); AST castExprListNode = getASTFactory().create( HqlSqlTokenTypes.EXPR_LIST, "exprList" ); methodIdentNode.setNextSibling( castExprListNode ); castExprListNode.addChild( versionValueNode ); versionValueNode.setNextSibling( getASTFactory().create( HqlSqlTokenTypes.IDENT, sessionFactoryHelper.getFactory().getDialect().getTypeName( sqlTypes[0] ) ) ); processFunction( versionMethodNode, true ); versionValueNode = versionMethodNode; } } else { if ( isIntegral( versionType ) ) { try { Object seedValue = versionType.seed( null ); versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, seedValue.toString() ); } catch( Throwable t ) { throw new QueryException( "could not determine seed value for version on bulk insert [" + versionType + "]" ); } } else if ( isDatabaseGeneratedTimestamp( versionType ) ) { String functionName = sessionFactoryHelper.getFactory().getDialect().getCurrentTimestampSQLFunctionName(); versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, functionName ); } else { throw new QueryException( "cannot handle version type [" + versionType + "] on bulk inserts with dialects not supporting parameters in insert-select statements" ); } } AST currentFirstSelectExprNode = selectClause.getFirstChild(); selectClause.setFirstChild( versionValueNode ); versionValueNode.setNextSibling( currentFirstSelectExprNode ); insertStatement.getIntoClause().prependVersionColumnSpec(); } if ( insertStatement.getIntoClause().isDiscriminated() ) { String sqlValue = insertStatement.getIntoClause().getQueryable().getDiscriminatorSQLValue(); AST discrimValue = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, sqlValue ); insertStatement.getSelectClause().addChild( discrimValue ); } } private boolean isDatabaseGeneratedTimestamp(Type type) { // currently only the Hibernate-supplied DbTimestampType is supported here return DbTimestampType.class.isAssignableFrom( type.getClass() ); } private boolean isIntegral(Type type) { return Long.class.isAssignableFrom( type.getReturnedClass() ) || Integer.class.isAssignableFrom( type.getReturnedClass() ) || long.class.isAssignableFrom( type.getReturnedClass() ) || int.class.isAssignableFrom( type.getReturnedClass() ); } private void useSelectClause(AST select) throws SemanticException { selectClause = ( SelectClause ) select; selectClause.initializeExplicitSelectClause( currentFromClause ); } private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticException { AST select = astFactory.create( SELECT_CLAUSE, "{derived select clause}" ); AST sibling = qn.getFromClause(); qn.setFirstChild( select ); select.setNextSibling( sibling ); selectClause = ( SelectClause ) select; selectClause.initializeDerivedSelectClause( currentFromClause ); LOG.debugf("Derived SELECT clause created."); } @Override protected void resolve(AST node) throws SemanticException { if ( node != null ) { // This is called when it's time to fully resolve a path expression. ResolvableNode r = ( ResolvableNode ) node; if ( isInFunctionCall() ) { r.resolveInFunctionCall( false, true ); } else { r.resolve( false, true ); // Generate implicit joins, only if necessary. } } } @Override protected void resolveSelectExpression(AST node) throws SemanticException { // This is called when it's time to fully resolve a path expression. int type = node.getType(); switch ( type ) { case DOT: { DotNode dot = ( DotNode ) node; dot.resolveSelectExpression(); break; } case ALIAS_REF: { // Notify the FROM element that it is being referenced by the select. FromReferenceNode aliasRefNode = ( FromReferenceNode ) node; //aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here? aliasRefNode.resolve( false, false ); //TODO: is it kosher to do it here? FromElement fromElement = aliasRefNode.getFromElement(); if ( fromElement != null ) { fromElement.setIncludeSubclasses( true ); } break; } default: { break; } } } @Override protected void beforeSelectClause() throws SemanticException { // Turn off includeSubclasses on all FromElements. FromClause from = getCurrentFromClause(); List fromElements = from.getFromElements(); for ( Iterator iterator = fromElements.iterator(); iterator.hasNext(); ) { FromElement fromElement = ( FromElement ) iterator.next(); fromElement.setIncludeSubclasses( false ); } } @Override protected AST generatePositionalParameter(AST inputNode) throws SemanticException { if ( namedParameters.size() > 0 ) { throw new SemanticException( "cannot define positional parameter after any named parameters have been defined" ); } ParameterNode parameter = ( ParameterNode ) astFactory.create( PARAM, "?" ); PositionalParameterSpecification paramSpec = new PositionalParameterSpecification( inputNode.getLine(), inputNode.getColumn(), positionalParameterCount++ ); parameter.setHqlParameterSpecification( paramSpec ); parameters.add( paramSpec ); return parameter; } @Override protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException { String name = nameNode.getText(); trackNamedParameterPositions( name ); // create the node initially with the param name so that it shows // appropriately in the "original text" attribute ParameterNode parameter = ( ParameterNode ) astFactory.create( NAMED_PARAM, name ); parameter.setText( "?" ); NamedParameterSpecification paramSpec = new NamedParameterSpecification( delimiterNode.getLine(), delimiterNode.getColumn(), name ); parameter.setHqlParameterSpecification( paramSpec ); parameters.add( paramSpec ); return parameter; } private void trackNamedParameterPositions(String name) { Integer loc = parameterCount++; Object o = namedParameters.get( name ); if ( o == null ) { namedParameters.put( name, loc ); } else if ( o instanceof Integer ) { ArrayList list = new ArrayList( 4 ); list.add( o ); list.add( loc ); namedParameters.put( name, list ); } else { ( ( ArrayList ) o ).add( loc ); } } @Override protected void processConstant(AST constant) throws SemanticException { literalProcessor.processConstant( constant, true ); // Use the delegate, resolve identifiers as FROM element aliases. } @Override protected void processBoolean(AST constant) throws SemanticException { literalProcessor.processBoolean( constant ); // Use the delegate. } @Override protected void processNumericLiteral(AST literal) { literalProcessor.processNumeric( literal ); } @Override protected void processIndex(AST indexOp) throws SemanticException { IndexNode indexNode = ( IndexNode ) indexOp; indexNode.resolve( true, true ); } @Override protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException { MethodNode methodNode = ( MethodNode ) functionCall; methodNode.resolve( inSelect ); } @Override protected void processAggregation(AST node, boolean inSelect) throws SemanticException { AggregateNode aggregateNode = ( AggregateNode ) node; aggregateNode.resolve(); } @Override protected void processConstructor(AST constructor) throws SemanticException { ConstructorNode constructorNode = ( ConstructorNode ) constructor; constructorNode.prepare(); } @Override protected void setAlias(AST selectExpr, AST ident) { ((SelectExpression) selectExpr).setAlias(ident.getText()); // only put the alias (i.e., result variable) in selectExpressionsByResultVariable // if is not defined in a subquery. if ( ! isSubQuery() ) { selectExpressionsByResultVariable.put( ident.getText(), ( SelectExpression ) selectExpr ); } } @Override protected boolean isOrderExpressionResultVariableRef(AST orderExpressionNode) throws SemanticException { // ORDER BY is not supported in a subquery // TODO: should an exception be thrown if an ORDER BY is in a subquery? if ( ! isSubQuery() && orderExpressionNode.getType() == IDENT && selectExpressionsByResultVariable.containsKey( orderExpressionNode.getText() ) ) { return true; } return false; } @Override protected void handleResultVariableRef(AST resultVariableRef) throws SemanticException { if ( isSubQuery() ) { throw new SemanticException( "References to result variables in subqueries are not supported." ); } ( ( ResultVariableRefNode ) resultVariableRef ).setSelectExpression( selectExpressionsByResultVariable.get( resultVariableRef.getText() ) ); } /** * Returns the locations of all occurrences of the named parameter. */ public int[] getNamedParameterLocations(String name) throws QueryException { Object o = namedParameters.get( name ); if ( o == null ) { QueryException qe = new QueryException( QueryTranslator.ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR + name ); qe.setQueryString( queryTranslatorImpl.getQueryString() ); throw qe; } if ( o instanceof Integer ) { return new int[]{( ( Integer ) o ).intValue()}; } else { return ArrayHelper.toIntArray( (ArrayList) o ); } } public void addQuerySpaces(Serializable[] spaces) { querySpaces.addAll( Arrays.asList( spaces ) ); } public Type[] getReturnTypes() { return selectClause.getQueryReturnTypes(); } public String[] getReturnAliases() { return selectClause.getQueryReturnAliases(); } public SelectClause getSelectClause() { return selectClause; } public FromClause getFinalFromClause() { FromClause top = currentFromClause; while ( top.getParentFromClause() != null ) { top = top.getParentFromClause(); } return top; } public boolean isShallowQuery() { // select clauses for insert statements should alwasy be treated as shallow return getStatementType() == INSERT || queryTranslatorImpl.isShallowQuery(); } public Map getEnabledFilters() { return queryTranslatorImpl.getEnabledFilters(); } public LiteralProcessor getLiteralProcessor() { return literalProcessor; } public ASTPrinter getASTPrinter() { return printer; } public ArrayList getParameters() { return parameters; } public int getNumberOfParametersInSetClause() { return numberOfParametersInSetClause; } @Override protected void evaluateAssignment(AST eq) throws SemanticException { prepareLogicOperator( eq ); Queryable persister = getCurrentFromClause().getFromElement().getQueryable(); evaluateAssignment( eq, persister, -1 ); } private void evaluateAssignment(AST eq, Queryable persister, int targetIndex) { if ( persister.isMultiTable() ) { // no need to even collect this information if the persister is considered multi-table AssignmentSpecification specification = new AssignmentSpecification( eq, persister ); if ( targetIndex >= 0 ) { assignmentSpecifications.add( targetIndex, specification ); } else { assignmentSpecifications.add( specification ); } numberOfParametersInSetClause += specification.getParameters().length; } } public ArrayList getAssignmentSpecifications() { return assignmentSpecifications; } @Override protected AST createIntoClause(String path, AST propertySpec) throws SemanticException { Queryable persister = ( Queryable ) getSessionFactoryHelper().requireClassPersister( path ); IntoClause intoClause = ( IntoClause ) getASTFactory().create( INTO, persister.getEntityName() ); intoClause.setFirstChild( propertySpec ); intoClause.initialize( persister ); addQuerySpaces( persister.getQuerySpaces() ); return intoClause; } @Override protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticException { UpdateStatement updateStatement = ( UpdateStatement ) updateNode; FromClause fromClause = updateStatement.getFromClause(); if ( versioned != null ) { // Make sure that the persister is versioned Queryable persister = fromClause.getFromElement().getQueryable(); if ( !persister.isVersioned() ) { throw new SemanticException( "increment option specified for update of non-versioned entity" ); } VersionType versionType = persister.getVersionType(); if ( versionType instanceof UserVersionType ) { throw new SemanticException( "user-defined version types not supported for increment option" ); } AST eq = getASTFactory().create( HqlSqlTokenTypes.EQ, "=" ); AST versionPropertyNode = generateVersionPropertyNode( persister ); eq.setFirstChild( versionPropertyNode ); AST versionIncrementNode = null; if ( isTimestampBasedVersion( versionType ) ) { versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" ); ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType ); ( ( ParameterNode ) versionIncrementNode ).setHqlParameterSpecification( paramSpec ); parameters.add( 0, paramSpec ); } else { // Not possible to simply re-use the versionPropertyNode here as it causes // OOM errors due to circularity :( versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PLUS, "+" ); versionIncrementNode.setFirstChild( generateVersionPropertyNode( persister ) ); versionIncrementNode.addChild( getASTFactory().create( HqlSqlTokenTypes.IDENT, "1" ) ); } eq.addChild( versionIncrementNode ); evaluateAssignment( eq, persister, 0 ); AST setClause = updateStatement.getSetClause(); AST currentFirstSetElement = setClause.getFirstChild(); setClause.setFirstChild( eq ); eq.setNextSibling( currentFirstSetElement ); } } private boolean isTimestampBasedVersion(VersionType versionType) { final Class javaType = versionType.getReturnedClass(); return Date.class.isAssignableFrom( javaType ) || Calendar.class.isAssignableFrom( javaType ); } private AST generateVersionPropertyNode(Queryable persister) throws SemanticException { String versionPropertyName = persister.getPropertyNames()[ persister.getVersionProperty() ]; AST versionPropertyRef = getASTFactory().create( HqlSqlTokenTypes.IDENT, versionPropertyName ); AST versionPropertyNode = lookupNonQualifiedProperty( versionPropertyRef ); resolve( versionPropertyNode ); return versionPropertyNode; } @Override protected void prepareLogicOperator(AST operator) throws SemanticException { ( ( OperatorNode ) operator ).initialize(); } @Override protected void prepareArithmeticOperator(AST operator) throws SemanticException { ( ( OperatorNode ) operator ).initialize(); } @Override protected void validateMapPropertyExpression(AST node) throws SemanticException { try { FromReferenceNode fromReferenceNode = (FromReferenceNode) node; QueryableCollection collectionPersister = fromReferenceNode.getFromElement().getQueryableCollection(); if ( ! Map.class.isAssignableFrom( collectionPersister.getCollectionType().getReturnedClass() ) ) { throw new SemanticException( "node did not reference a map" ); } } catch ( SemanticException se ) { throw se; } catch ( Throwable t ) { throw new SemanticException( "node did not reference a map" ); } } public static void panic() { throw new QueryException( "TreeWalker: panic" ); } }