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

org.hibernate.engine.query.spi.HQLQueryPlan Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * 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 .
 */
package org.hibernate.engine.query.spi;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
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.Filter;
import org.hibernate.HibernateException;
import org.hibernate.QueryException;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.hql.internal.QuerySplitter;
import org.hibernate.hql.spi.FilterTranslator;
import org.hibernate.hql.spi.NamedParameterInformation;
import org.hibernate.hql.spi.ParameterTranslations;
import org.hibernate.hql.spi.PositionalParameterInformation;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.hql.spi.QueryTranslatorFactory;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.internal.util.collections.JoinedIterator;
import org.hibernate.query.internal.ParameterMetadataImpl;
import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.type.Type;

/**
 * Defines a query execution plan for an HQL query (or filter).
 *
 * @author Steve Ebersole
 */
public class HQLQueryPlan implements Serializable {
	private static final CoreMessageLogger LOG = CoreLogging.messageLogger( HQLQueryPlan.class );

	// TODO : keep separate notions of QT[] here for shallow/non-shallow queries...

	private final String sourceQuery;
	private final QueryTranslator[] translators;

	private final ParameterMetadataImpl parameterMetadata;
	private final ReturnMetadata returnMetadata;
	private final Set querySpaces;

	private final Set enabledFilterNames;
	private final boolean shallow;

	/**
	* We'll check the trace level only once per instance
	*/
	private final boolean traceEnabled = LOG.isTraceEnabled();

	/**
	 * Constructs a HQLQueryPlan
	 *
	 * @param hql The HQL query
	 * @param shallow Whether the execution is to be shallow or not
	 * @param enabledFilters The enabled filters (we only keep the names)
	 * @param factory The factory
	 */
	public HQLQueryPlan(String hql, boolean shallow, Map enabledFilters,
			SessionFactoryImplementor factory) {
		this( hql, null, shallow, enabledFilters, factory, null );
	}

	public HQLQueryPlan(String hql, boolean shallow, Map enabledFilters,
			SessionFactoryImplementor factory, EntityGraphQueryHint entityGraphQueryHint) {
		this( hql, null, shallow, enabledFilters, factory, entityGraphQueryHint );
	}

	@SuppressWarnings("unchecked")
	protected HQLQueryPlan(
			String hql,
			String collectionRole,
			boolean shallow,
			Map enabledFilters,
			SessionFactoryImplementor factory,
			EntityGraphQueryHint entityGraphQueryHint) {
		this.sourceQuery = hql;
		this.shallow = shallow;

		if ( enabledFilters.isEmpty() ) {
			this.enabledFilterNames = Collections.emptySet();
		}
		else {
			this.enabledFilterNames = Collections.unmodifiableSet( new HashSet<>( enabledFilters.keySet() ) );
		}

		final String[] concreteQueryStrings = QuerySplitter.concreteQueries( hql, factory );
		final int length = concreteQueryStrings.length;
		this.translators = new QueryTranslator[length];

		final Set combinedQuerySpaces = new HashSet<>();

		final Map querySubstitutions = factory.getSessionFactoryOptions().getQuerySubstitutions();
		final QueryTranslatorFactory queryTranslatorFactory = factory.getServiceRegistry().getService( QueryTranslatorFactory.class );


		for ( int i=0; i 1 ) ? new Type[translators[0].getReturnTypes().length] : translators[0].getReturnTypes();
				returnMetadata = new ReturnMetadata( translators[0].getReturnAliases(), types );
			}
		}
	}

	public String getSourceQuery() {
		return sourceQuery;
	}

	public Set getQuerySpaces() {
		return querySpaces;
	}

	public ParameterMetadataImpl getParameterMetadata() {
		return parameterMetadata;
	}

	public ReturnMetadata getReturnMetadata() {
		return returnMetadata;
	}

	public Set getEnabledFilterNames() {
		return enabledFilterNames;
	}

	/**
	 * This method should only be called for debugging purposes as it regenerates a new array every time.
	 */
	public String[] getSqlStrings() {
		List sqlStrings = new ArrayList<>();
		for ( int i = 0; i < translators.length; i++ ) {
			sqlStrings.addAll( translators[i].collectSqlStrings() );
		}
		return ArrayHelper.toStringArray( sqlStrings );
	}

	public Set getUtilizedFilterNames() {
		// TODO : add this info to the translator and aggregate it here...
		return null;
	}

	public boolean isShallow() {
		return shallow;
	}

	/**
	 * Coordinates the efforts to perform a list across all the included query translators.
	 *
	 * @param queryParameters The query parameters
	 * @param session The session
	 *
	 * @return The query result list
	 *
	 * @throws HibernateException Indicates a problem performing the query
	 */
	@SuppressWarnings("unchecked")
	public List performList(
			QueryParameters queryParameters,
			SharedSessionContractImplementor session) throws HibernateException {
		if ( traceEnabled ) {
			LOG.tracev( "Find: {0}", getSourceQuery() );
			queryParameters.traceParameters( session.getFactory() );
		}

		final RowSelection rowSelection = queryParameters.getRowSelection();
		final boolean hasLimit = rowSelection != null
				&& rowSelection.definesLimits();
		final boolean needsLimit = hasLimit && translators.length > 1;

		final QueryParameters queryParametersToUse;
		if ( needsLimit ) {
			LOG.needsLimit();
			final RowSelection selection = new RowSelection();
			selection.setFetchSize( queryParameters.getRowSelection().getFetchSize() );
			selection.setTimeout( queryParameters.getRowSelection().getTimeout() );
			queryParametersToUse = queryParameters.createCopyUsing( selection );
		}
		else {
			queryParametersToUse = queryParameters;
		}

		//fast path to avoid unnecessary allocation and copying
		if ( translators.length == 1 ) {
			return translators[0].list( session, queryParametersToUse );
		}
		final int guessedResultSize = guessResultSize( rowSelection );
		final List combinedResults = new ArrayList( guessedResultSize );
		final IdentitySet distinction;
		if ( needsLimit ) {
			distinction = new IdentitySet( guessedResultSize );
		}
		else {
			distinction = null;
		}
		int includedCount = -1;
		translator_loop:
		for ( QueryTranslator translator : translators ) {
			final List tmp = translator.list( session, queryParametersToUse );
			if ( needsLimit ) {
				// NOTE : firstRow is zero-based
				final int first = queryParameters.getRowSelection().getFirstRow() == null
						? 0
						: queryParameters.getRowSelection().getFirstRow();
				final int max = queryParameters.getRowSelection().getMaxRows() == null
						? -1
						: queryParameters.getRowSelection().getMaxRows();
				for ( final Object result : tmp ) {
					if ( !distinction.add( result ) ) {
						continue;
					}
					includedCount++;
					if ( includedCount < first ) {
						continue;
					}
					combinedResults.add( result );
					if ( max >= 0 && includedCount > max ) {
						// break the outer loop !!!
						break translator_loop;
					}
				}
			}
			else {
				combinedResults.addAll( tmp );
			}
		}
		return combinedResults;
	}

	/**
	 * If we're able to guess a likely size of the results we can optimize allocation
	 * of our datastructures.
	 * Essentially if we detect the user is not using pagination, we attempt to use the FetchSize
	 * as a reasonable hint. If fetch size is not being set either, it is reasonable to expect
	 * that we're going to have a single hit. In such a case it would be tempting to return a constant
	 * of value one, but that's dangerous as it doesn't scale up appropriately for example
	 * with an ArrayList if the guess is wrong.
	 *
	 * @param rowSelection
	 * @return a reasonable size to use for allocation
	 */
	@SuppressWarnings("UnnecessaryUnboxing")
	private int guessResultSize(RowSelection rowSelection) {
		if ( rowSelection != null ) {
			final int maxReasonableAllocation = rowSelection.getFetchSize() != null ? rowSelection.getFetchSize().intValue() : 100;
			if ( rowSelection.getMaxRows() != null && rowSelection.getMaxRows().intValue() > 0 ) {
				return Math.min( maxReasonableAllocation, rowSelection.getMaxRows().intValue() );
			}
			else if ( rowSelection.getFetchSize() != null && rowSelection.getFetchSize().intValue() > 0 ) {
				return rowSelection.getFetchSize().intValue();
			}
		}
		return 7;//magic number guessed as a reasonable default.
	}

	/**
	 * Coordinates the efforts to perform an iterate across all the included query translators.
	 *
	 * @param queryParameters The query parameters
	 * @param session The session
	 *
	 * @return The query result iterator
	 *
	 * @throws HibernateException Indicates a problem performing the query
	 */
	@SuppressWarnings("unchecked")
	public Iterator performIterate(
			QueryParameters queryParameters,
			EventSource session) throws HibernateException {
		if ( traceEnabled ) {
			LOG.tracev( "Iterate: {0}", getSourceQuery() );
			queryParameters.traceParameters( session.getFactory() );
		}
		if ( translators.length == 0 ) {
			return Collections.emptyIterator();
		}

		final boolean many = translators.length > 1;
		Iterator[] results = null;
		if ( many ) {
			results = new Iterator[translators.length];
		}

		Iterator result = null;
		for ( int i = 0; i < translators.length; i++ ) {
			result = translators[i].iterate( queryParameters, session );
			if ( many ) {
				results[i] = result;
			}
		}

		return many ? new JoinedIterator( results ) : result;
	}

	/**
	 * Coordinates the efforts to perform a scroll across all the included query translators.
	 *
	 * @param queryParameters The query parameters
	 * @param session The session
	 *
	 * @return The query result iterator
	 *
	 * @throws HibernateException Indicates a problem performing the query
	 */
	public ScrollableResultsImplementor performScroll(
			QueryParameters queryParameters,
			SharedSessionContractImplementor session) throws HibernateException {
		if ( traceEnabled ) {
			LOG.tracev( "Iterate: {0}", getSourceQuery() );
			queryParameters.traceParameters( session.getFactory() );
		}
		if ( translators.length != 1 ) {
			throw new QueryException( "implicit polymorphism not supported for scroll() queries" );
		}
		if ( queryParameters.getRowSelection().definesLimits() && translators[0].containsCollectionFetches() ) {
			throw new QueryException( "firstResult/maxResults not supported in conjunction with scroll() of a query containing collection fetches" );
		}

		return translators[0].scroll( queryParameters, session );
	}

	/**
	 * Coordinates the efforts to perform an execution across all the included query translators.
	 *
	 * @param queryParameters The query parameters
	 * @param session The session
	 *
	 * @return The aggregated "affected row" count
	 *
	 * @throws HibernateException Indicates a problem performing the execution
	 */
	public int performExecuteUpdate(QueryParameters queryParameters, SharedSessionContractImplementor session)
			throws HibernateException {
		if ( traceEnabled ) {
			LOG.tracev( "Execute update: {0}", getSourceQuery() );
			queryParameters.traceParameters( session.getFactory() );
		}
		if ( translators.length != 1 ) {
			LOG.splitQueries( getSourceQuery(), translators.length );
		}
		int result = 0;
		for ( QueryTranslator translator : translators ) {
			result += translator.executeUpdate( queryParameters, session );
		}
		return result;
	}

	private ParameterMetadataImpl buildParameterMetadata(ParameterTranslations parameterTranslations, String hql) {
		final Map ordinalParamDescriptors;
		if ( parameterTranslations.getPositionalParameterInformationMap().isEmpty() ) {
			ordinalParamDescriptors = Collections.emptyMap();
		}
		else {
			final Map temp = new HashMap<>();
			for ( Map.Entry entry :
					parameterTranslations.getPositionalParameterInformationMap().entrySet() ) {
				final int position = entry.getKey();
				temp.put(
						position,
						new OrdinalParameterDescriptor(
								position,
								position - 1,
								entry.getValue().getExpectedType(),
								entry.getValue().getSourceLocations()
						)
				);
			}
			ordinalParamDescriptors = Collections.unmodifiableMap( temp );
		}


		final Map namedParamDescriptorMap;

		if ( parameterTranslations.getNamedParameterInformationMap().isEmpty() ) {
			namedParamDescriptorMap = Collections.emptyMap();
		}
		else {
			final Map tmp = new HashMap<>();
			for ( Map.Entry namedEntry :
					parameterTranslations.getNamedParameterInformationMap().entrySet() ) {
				final String name = namedEntry.getKey();
				tmp.put(
						name,
						new NamedParameterDescriptor(
								name,
								parameterTranslations.getNamedParameterInformation( name ).getExpectedType(),
								namedEntry.getValue().getSourceLocations()
						)
				);
			}

			namedParamDescriptorMap = Collections.unmodifiableMap( tmp );
		}


		return new ParameterMetadataImpl( ordinalParamDescriptors, namedParamDescriptorMap );
	}

	/**
	 * Access to the underlying translators associated with this query
	 *
	 * @return The translators
	 */
	public QueryTranslator[] getTranslators() {
		final QueryTranslator[] copy = new QueryTranslator[translators.length];
		System.arraycopy( translators, 0, copy, 0, copy.length );
		return copy;
	}

	public Class getDynamicInstantiationResultType() {
		return translators[0].getDynamicInstantiationResultType();
	}

	public boolean isSelect() {
		return !translators[0].isManipulationStatement();
	}

	public boolean isUpdate() {
		return translators[0].isUpdateStatement();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy