All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.engine.internal.JoinSequence Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2008-2011, Red Hat Inc. 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 Inc.
 *
 * 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.engine.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.JoinType;
import org.hibernate.sql.QueryJoinFragment;
import org.hibernate.type.AssociationType;

/**
 * A sequence of {@link Join} delegates to make it "easier" to work with joins.  The "easier" part is obviously
 * subjective ;)
 * 

* Additionally JoinSequence is a directed graph of other JoinSequence instances, as represented by the * {@link #next} ({@link #setNext(JoinSequence)}) pointer. * * @author Gavin King * @author Steve Ebersole * * @see JoinFragment */ public class JoinSequence { private final SessionFactoryImplementor factory; private final StringBuilder conditions = new StringBuilder(); private final List joins = new ArrayList(); private boolean useThetaStyle; private String rootAlias; private Joinable rootJoinable; private Selector selector; private JoinSequence next; private boolean isFromPart; /** * Constructs a JoinSequence * * @param factory The SessionFactory */ public JoinSequence(SessionFactoryImplementor factory) { this.factory = factory; } /** * Retrieve a JoinSequence that represents just the FROM clause parts * * @return The JoinSequence that represents just the FROM clause parts */ public JoinSequence getFromPart() { final JoinSequence fromPart = new JoinSequence( factory ); fromPart.joins.addAll( this.joins ); fromPart.useThetaStyle = this.useThetaStyle; fromPart.rootAlias = this.rootAlias; fromPart.rootJoinable = this.rootJoinable; fromPart.selector = this.selector; fromPart.next = this.next == null ? null : this.next.getFromPart(); fromPart.isFromPart = true; return fromPart; } private Set treatAsDeclarations; public void applyTreatAsDeclarations(Set treatAsDeclarations) { if ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) { return; } if ( this.treatAsDeclarations == null ) { this.treatAsDeclarations = new HashSet(); } this.treatAsDeclarations.addAll( treatAsDeclarations ); } /** * Create a full, although shallow, copy. * * @return The copy */ public JoinSequence copy() { final JoinSequence copy = new JoinSequence( factory ); copy.joins.addAll( this.joins ); copy.useThetaStyle = this.useThetaStyle; copy.rootAlias = this.rootAlias; copy.rootJoinable = this.rootJoinable; copy.selector = this.selector; copy.next = this.next == null ? null : this.next.copy(); copy.isFromPart = this.isFromPart; copy.conditions.append( this.conditions.toString() ); return copy; } /** * Add a join to this sequence * * @param associationType The type of the association representing the join * @param alias The RHS alias for the join * @param joinType The type of join (INNER, etc) * @param referencingKey The LHS columns for the join condition * * @return The Join memento * * @throws MappingException Generally indicates a problem resolving the associationType to a {@link Joinable} */ public JoinSequence addJoin( AssociationType associationType, String alias, JoinType joinType, String[] referencingKey) throws MappingException { joins.add( new Join( factory, associationType, alias, joinType, referencingKey ) ); return this; } /** * Generate a JoinFragment * * @return The JoinFragment * * @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata */ public JoinFragment toJoinFragment() throws MappingException { return toJoinFragment( Collections.EMPTY_MAP, true ); } /** * Generate a JoinFragment * * @param enabledFilters The filters associated with the originating session to properly define join conditions * @param includeAllSubclassJoins Should all subclass joins be added to the rendered JoinFragment? * * @return The JoinFragment * * @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata */ public JoinFragment toJoinFragment(Map enabledFilters, boolean includeAllSubclassJoins) throws MappingException { return toJoinFragment( enabledFilters, includeAllSubclassJoins, null, null ); } /** * Generate a JoinFragment * * @param enabledFilters The filters associated with the originating session to properly define join conditions * @param includeAllSubclassJoins Should all subclass joins be added to the rendered JoinFragment? * @param withClauseFragment The with clause (which represents additional join restrictions) fragment * @param withClauseJoinAlias The * * @return The JoinFragment * * @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata */ public JoinFragment toJoinFragment( Map enabledFilters, boolean includeAllSubclassJoins, String withClauseFragment, String withClauseJoinAlias) throws MappingException { final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle ); if ( rootJoinable != null ) { joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias ); final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters, treatAsDeclarations ); // JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it // can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track // of that fact. joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) ); addSubclassJoins( joinFragment, rootAlias, rootJoinable, true, includeAllSubclassJoins, treatAsDeclarations ); } Joinable last = rootJoinable; for ( Join join : joins ) { // technically the treatAsDeclarations should only apply to rootJoinable or to a single Join, // but that is not possible atm given how these JoinSequence and Join objects are built. // However, it is generally ok given how the HQL parser builds these JoinSequences (a HQL join // results in a JoinSequence with an empty rootJoinable and a single Join). So we use that here // as an assumption final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters, treatAsDeclarations ); String condition; if ( last != null && isManyToManyRoot( last ) && ((QueryableCollection) last).getElementType() == join.getAssociationType() ) { // the current join represents the join between a many-to-many association table // and its "target" table. Here we need to apply any additional filters // defined specifically on the many-to-many final String manyToManyFilter = ( (QueryableCollection) last ).getManyToManyFilterFragment( join.getAlias(), enabledFilters ); condition = "".equals( manyToManyFilter ) ? on : "".equals( on ) ? manyToManyFilter : on + " and " + manyToManyFilter; } else { condition = on; } if ( withClauseFragment != null && !isManyToManyRoot( join.joinable )) { condition += " and " + withClauseFragment; } joinFragment.addJoin( join.getJoinable().getTableName(), join.getAlias(), join.getLHSColumns(), JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ), join.joinType, condition ); addSubclassJoins( joinFragment, join.getAlias(), join.getJoinable(), join.joinType == JoinType.INNER_JOIN, includeAllSubclassJoins, // ugh.. this is needed because of how HQL parser (FromElementFactory/SessionFactoryHelper) // builds the JoinSequence for HQL joins treatAsDeclarations ); last = join.getJoinable(); } if ( next != null ) { joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeAllSubclassJoins ) ); } joinFragment.addCondition( conditions.toString() ); if ( isFromPart ) { joinFragment.clearWherePart(); } return joinFragment; } @SuppressWarnings("SimplifiableIfStatement") private boolean isManyToManyRoot(Joinable joinable) { if ( joinable != null && joinable.isCollection() ) { return ( (QueryableCollection) joinable ).isManyToMany(); } return false; } private void addSubclassJoins( JoinFragment joinFragment, String alias, Joinable joinable, boolean innerJoin, boolean includeSubclassJoins, Set treatAsDeclarations) { final boolean include = includeSubclassJoins && isIncluded( alias ); joinFragment.addJoins( joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations ), joinable.whereJoinFragment( alias, innerJoin, include, treatAsDeclarations ) ); } private boolean isIncluded(String alias) { return selector != null && selector.includeSubclasses( alias ); } /** * Add a condition to this sequence. * * @param condition The condition * * @return {@link this}, for method chaining */ public JoinSequence addCondition(String condition) { if ( condition.trim().length() != 0 ) { if ( !condition.startsWith( " and " ) ) { conditions.append( " and " ); } conditions.append( condition ); } return this; } /** * Add a condition to this sequence. Typical usage here might be: *

	 *     addCondition( "a", {"c1", "c2"}, "?" )
	 * 
* to represent: *
	 *     "... a.c1 = ? and a.c2 = ? ..."
	 * 
* * @param alias The alias to apply to the columns * @param columns The columns to add checks for * @param condition The conditions to check against the columns * * @return {@link this}, for method chaining */ public JoinSequence addCondition(String alias, String[] columns, String condition) { for ( String column : columns ) { conditions.append( " and " ) .append( alias ) .append( '.' ) .append( column ) .append( condition ); } return this; } /** * Set the root of this JoinSequence. In SQL terms, this would be the driving table. * * @param joinable The entity/collection that is the root of this JoinSequence * @param alias The alias associated with that joinable. * * @return {@link this}, for method chaining */ public JoinSequence setRoot(Joinable joinable, String alias) { this.rootAlias = alias; this.rootJoinable = joinable; return this; } /** * Sets the next join sequence * * @param next The next JoinSequence in the directed graph * * @return {@code this}, for method chaining */ public JoinSequence setNext(JoinSequence next) { this.next = next; return this; } /** * Set the Selector to use to determine how subclass joins should be applied. * * @param selector The selector to apply * * @return {@code this}, for method chaining */ public JoinSequence setSelector(Selector selector) { this.selector = selector; return this; } /** * Should this JoinSequence use theta-style joining (both a FROM and WHERE component) in the rendered SQL? * * @param useThetaStyle {@code true} indicates that theta-style joins should be used. * * @return {@code this}, for method chaining */ public JoinSequence setUseThetaStyle(boolean useThetaStyle) { this.useThetaStyle = useThetaStyle; return this; } public boolean isThetaStyle() { return useThetaStyle; } public Join getFirstJoin() { return joins.get( 0 ); } /** * A subclass join selector */ public static interface Selector { /** * Should subclasses be included in the rendered join sequence? * * @param alias The alias * * @return {@code true} if the subclass joins should be included */ public boolean includeSubclasses(String alias); } /** * Represents a join */ public static final class Join { private final AssociationType associationType; private final Joinable joinable; private final JoinType joinType; private final String alias; private final String[] lhsColumns; Join( SessionFactoryImplementor factory, AssociationType associationType, String alias, JoinType joinType, String[] lhsColumns) throws MappingException { this.associationType = associationType; this.joinable = associationType.getAssociatedJoinable( factory ); this.alias = alias; this.joinType = joinType; this.lhsColumns = lhsColumns; } public String getAlias() { return alias; } public AssociationType getAssociationType() { return associationType; } public Joinable getJoinable() { return joinable; } public JoinType getJoinType() { return joinType; } public String[] getLHSColumns() { return lhsColumns; } @Override public String toString() { return joinable.toString() + '[' + alias + ']'; } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append( "JoinSequence{" ); if ( rootJoinable != null ) { buf.append( rootJoinable ) .append( '[' ) .append( rootAlias ) .append( ']' ); } for ( Join join : joins ) { buf.append( "->" ).append( join ); } return buf.append( '}' ).toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy