/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.hql.internal.ast.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hibernate.AssertionFailure;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
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.ImpliedFromElement;
import org.hibernate.hql.internal.ast.tree.ParameterContainer;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.classic.ParserHelper;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.FilterImpl;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.param.DynamicFilterParameterSpecification;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.JoinType;
import org.hibernate.type.Type;

Performs the post-processing of the join information gathered during semantic analysis. The join generating classes are complex, this encapsulates some of the JoinSequence-related code.
Author:Joshua Davis
/** * Performs the post-processing of the join information gathered during semantic analysis. * The join generating classes are complex, this encapsulates some of the JoinSequence-related * code. * * @author Joshua Davis */
public class JoinProcessor implements SqlTokenTypes { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( JoinProcessor.class ); private static final Pattern DYNAMIC_FILTER_PATTERN = Pattern.compile(":(\\w+\\S*)\\s"); private static final String LITERAL_DELIMITER = "'"; private final HqlSqlWalker walker; private final SyntheticAndFactory syntheticAndFactory;
Constructs a new JoinProcessor.
Params:
  • walker – The walker to which we are bound, giving us access to needed resources.
/** * Constructs a new JoinProcessor. * * @param walker The walker to which we are bound, giving us access to needed resources. */
public JoinProcessor(HqlSqlWalker walker) { this.walker = walker; this.syntheticAndFactory = new SyntheticAndFactory( walker ); }
Translates an AST join type (i.e., the token type) into a JoinFragment.XXX join type.
Params:
  • astJoinType – The AST join type (from HqlSqlTokenTypes or SqlTokenTypes)
See Also:
Returns:a JoinFragment.XXX join type.
/** * Translates an AST join type (i.e., the token type) into a JoinFragment.XXX join type. * * @param astJoinType The AST join type (from HqlSqlTokenTypes or SqlTokenTypes) * * @return a JoinFragment.XXX join type. * * @see JoinFragment * @see SqlTokenTypes */
public static JoinType toHibernateJoinType(int astJoinType) { switch ( astJoinType ) { case LEFT_OUTER: { return JoinType.LEFT_OUTER_JOIN; } case INNER: { return JoinType.INNER_JOIN; } case RIGHT_OUTER: { return JoinType.RIGHT_OUTER_JOIN; } case FULL: { return JoinType.FULL_JOIN; } default: { throw new AssertionFailure( "undefined join type " + astJoinType ); } } } public void processJoins(QueryNode query) { final FromClause fromClause = query.getFromClause(); final List fromElements; if ( DotNode.useThetaStyleImplicitJoins ) { // for regression testing against output from the old parser... // found it easiest to simply reorder the FromElements here into ascending order // in terms of injecting them into the resulting sql ast in orders relative to those // expected by the old parser; this is definitely another of those "only needed // for regression purposes". The SyntheticAndFactory, then, simply injects them as it // encounters them. fromElements = new ArrayList(); ListIterator liter = fromClause.getFromElements().listIterator( fromClause.getFromElements().size() ); while ( liter.hasPrevious() ) { fromElements.add( liter.previous() ); } } else { fromElements = new ArrayList( fromClause.getFromElements().size() ); ListIterator<FromElement> liter = fromClause.getFromElements().listIterator(); while ( liter.hasNext() ) { FromElement fromElement = liter.next(); // We found an implied from element that is used in the WITH clause of another from element, so it need to become part of it's join sequence if ( fromElement instanceof ImpliedFromElement && fromElement.getOrigin().getWithClauseFragment() != null && fromElement.getOrigin().getWithClauseFragment().contains( fromElement.getTableAlias() ) ) { fromElement.getOrigin().getJoinSequence().addJoin( (ImpliedFromElement) fromElement ); // This from element will be rendered as part of the origins join sequence fromElement.setText( "" ); } else { fromElements.add( fromElement ); } } } // Iterate through the alias,JoinSequence pairs and generate SQL token nodes. Iterator iter = fromElements.iterator(); while ( iter.hasNext() ) { final FromElement fromElement = (FromElement) iter.next(); JoinSequence join = fromElement.getJoinSequence(); join.setSelector( new JoinSequence.Selector() { public boolean includeSubclasses(String alias) { // The uber-rule here is that we need to include subclass joins if // the FromElement is in any way dereferenced by a property from // the subclass table; otherwise we end up with column references // qualified by a non-existent table reference in the resulting SQL... boolean containsTableAlias = fromClause.containsTableAlias( alias ); if ( fromElement.isDereferencedBySubclassProperty() ) { // TODO : or should we return 'containsTableAlias'?? LOG.tracev( "Forcing inclusion of extra joins [alias={0}, containsTableAlias={1}]", alias, containsTableAlias ); return true; } boolean shallowQuery = walker.isShallowQuery(); boolean includeSubclasses = fromElement.isIncludeSubclasses(); boolean subQuery = fromClause.isSubQuery(); return includeSubclasses && containsTableAlias && !subQuery && !shallowQuery; } } ); addJoinNodes( query, join, fromElement ); } } private void addJoinNodes(QueryNode query, JoinSequence join, FromElement fromElement) { JoinFragment joinFragment = join.toJoinFragment( walker.getEnabledFilters(), fromElement.useFromFragment() || fromElement.isDereferencedBySuperclassOrSubclassProperty(), fromElement.getWithClauseFragment() ); String frag = joinFragment.toFromFragmentString(); String whereFrag = joinFragment.toWhereFragmentString(); // If the from element represents a JOIN_FRAGMENT and it is // a theta-style join, convert its type from JOIN_FRAGMENT // to FROM_FRAGMENT if ( fromElement.getType() == JOIN_FRAGMENT && ( join.isThetaStyle() || StringHelper.isNotEmpty( whereFrag ) ) ) { fromElement.setType( FROM_FRAGMENT ); fromElement.getJoinSequence().setUseThetaStyle( true ); // this is used during SqlGenerator processing } // If there is a FROM fragment and the FROM element is an explicit, then add the from part. if ( fromElement.useFromFragment() || ( fromElement.getFromClause().isSubQuery() && fromElement.isDereferencedBySuperclassOrSubclassProperty() ) /*&& StringHelper.isNotEmpty( frag )*/ ) { String fromFragment = processFromFragment( frag, join ).trim(); LOG.debugf( "Using FROM fragment [%s]", fromFragment ); processDynamicFilterParameters( fromFragment, fromElement, walker ); } syntheticAndFactory.addWhereFragment( joinFragment, whereFrag, query, fromElement, walker ); } private String processFromFragment(String frag, JoinSequence join) { String fromFragment = frag.trim(); // The FROM fragment will probably begin with ', '. Remove this if it is present. if ( fromFragment.startsWith( ", " ) ) { fromFragment = fromFragment.substring( 2 ); } return fromFragment; } public static void processDynamicFilterParameters( final String sqlFragment, final ParameterContainer container, final HqlSqlWalker walker) { if ( walker.getEnabledFilters().isEmpty() && ( !hasDynamicFilterParam( walker, sqlFragment ) ) && ( !( hasCollectionFilterParam( sqlFragment ) ) ) ) { return; } Dialect dialect = walker.getDialect(); String symbols = ParserHelper.HQL_SEPARATORS + dialect.openQuote() + dialect.closeQuote(); StringTokenizer tokens = new StringTokenizer( sqlFragment, symbols, true ); StringBuilder result = new StringBuilder(); while ( tokens.hasMoreTokens() ) { final String token = tokens.nextToken(); if ( token.startsWith( ParserHelper.HQL_VARIABLE_PREFIX ) ) { final String filterParameterName = token.substring( 1 ); final String[] parts = LoadQueryInfluencers.parseFilterParameterName( filterParameterName ); final FilterImpl filter = (FilterImpl) walker.getEnabledFilters().get( parts[0] ); final Object value = filter.getParameter( parts[1] ); final Type type = filter.getFilterDefinition().getParameterType( parts[1] ); final String typeBindFragment = String.join( ",", ArrayHelper.fillArray( "?", type.getColumnSpan( walker.getSessionFactoryHelper().getFactory() ) ) ); final String bindFragment; if ( value != null && Collection.class.isInstance( value ) ) { bindFragment = String.join( ",", ArrayHelper.fillArray( typeBindFragment, ( (Collection) value ).size() ) ); } else { bindFragment = typeBindFragment; } result.append( bindFragment ); container.addEmbeddedParameter( new DynamicFilterParameterSpecification( parts[0], parts[1], type ) ); } else { result.append( token ); } } container.setText( result.toString() ); } private static boolean hasDynamicFilterParam(HqlSqlWalker walker, String sqlFragment) { String closeQuote = String.valueOf( walker.getDialect().closeQuote() ); Matcher matcher = DYNAMIC_FILTER_PATTERN.matcher( sqlFragment ); if ( matcher.find() && matcher.groupCount() > 0 ) { String match = matcher.group( 1 ); return match.endsWith( closeQuote ) || match.endsWith( LITERAL_DELIMITER ); } return true; } private static boolean hasCollectionFilterParam(String sqlFragment) { return !sqlFragment.contains( "?" ); } }