package org.hibernate.loader.hql;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.hql.internal.HolderInstantiator;
import org.hibernate.hql.internal.ast.QueryTranslatorImpl;
import org.hibernate.hql.internal.ast.tree.AggregatedSelectExpression;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.hql.spi.NamedParameterInformation;
import org.hibernate.hql.spi.ParameterInformation;
import org.hibernate.internal.IteratorImpl;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.BasicLoader;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.Lockable;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
public class QueryLoader extends BasicLoader {
private QueryTranslatorImpl queryTranslator;
private Queryable[] entityPersisters;
private String[] entityAliases;
private String[] sqlAliases;
private String[] sqlAliasSuffixes;
private boolean[] includeInSelect;
private String[] collectionSuffixes;
private boolean hasScalars;
private String[][] scalarColumnNames;
private Type[] queryReturnTypes;
private final Map<String, String> sqlAliasByEntityAlias = new HashMap<>( 8 );
private EntityType[] ownerAssociationTypes;
private int[] owners;
private boolean[] entityEagerPropertyFetches;
private int[] collectionOwners;
private QueryableCollection[] collectionPersisters;
private int selectLength;
private AggregatedSelectExpression aggregatedSelectExpression;
private String[] queryReturnAliases;
private LockMode[] defaultLockModes;
public QueryLoader(
final QueryTranslatorImpl queryTranslator,
final SessionFactoryImplementor factory,
final SelectClause selectClause) {
super( factory );
this.queryTranslator = queryTranslator;
initialize( selectClause );
postInstantiate();
}
private void initialize(SelectClause selectClause) {
List fromElementList = selectClause.getFromElementsForLoad();
hasScalars = selectClause.isScalarSelect();
scalarColumnNames = selectClause.getColumnNames();
queryReturnTypes = selectClause.getQueryReturnTypes();
aggregatedSelectExpression = selectClause.getAggregatedSelectExpression();
queryReturnAliases = selectClause.getQueryReturnAliases();
List collectionFromElements = selectClause.getCollectionFromElements();
if ( collectionFromElements != null && collectionFromElements.size() != 0 ) {
int length = collectionFromElements.size();
collectionPersisters = new QueryableCollection[length];
collectionOwners = new int[length];
collectionSuffixes = new String[length];
for ( int i = 0; i < length; i++ ) {
FromElement collectionFromElement = (FromElement) collectionFromElements.get( i );
collectionPersisters[i] = collectionFromElement.getQueryableCollection();
collectionOwners[i] = fromElementList.indexOf( collectionFromElement.getOrigin() );
collectionSuffixes[i] = collectionFromElement.getCollectionSuffix();
}
}
int size = fromElementList.size();
entityPersisters = new Queryable[size];
entityEagerPropertyFetches = new boolean[size];
entityAliases = new String[size];
sqlAliases = new String[size];
sqlAliasSuffixes = new String[size];
includeInSelect = new boolean[size];
owners = new int[size];
ownerAssociationTypes = new EntityType[size];
for ( int i = 0; i < size; i++ ) {
final FromElement element = (FromElement) fromElementList.get( i );
entityPersisters[i] = (Queryable) element.getEntityPersister();
if ( entityPersisters[i] == null ) {
throw new IllegalStateException( "No entity persister for " + element.toString() );
}
entityEagerPropertyFetches[i] = element.isAllPropertyFetch();
sqlAliases[i] = element.getTableAlias();
entityAliases[i] = element.getClassAlias();
sqlAliasByEntityAlias.put( entityAliases[i], sqlAliases[i] );
sqlAliasSuffixes[i] = ( size == 1 ) ? "" : (Integer.toString( i ) + "_").intern();
includeInSelect[i] = !element.isFetch();
if ( includeInSelect[i] ) {
selectLength++;
}
owners[i] = -1;
if ( element.isFetch() ) {
if ( element.isCollectionJoin() || element.getQueryableCollection() != null ) {
}
else if ( element.getDataType().isEntityType() ) {
EntityType entityType = (EntityType) element.getDataType();
if ( entityType.isOneToOne() ) {
owners[i] = fromElementList.indexOf( element.getOrigin() );
}
ownerAssociationTypes[i] = entityType;
}
}
}
defaultLockModes = ArrayHelper.fillArray( LockMode.NONE, size );
}
public AggregatedSelectExpression getAggregatedSelectExpression() {
return aggregatedSelectExpression;
}
public final void validateScrollability() throws HibernateException {
queryTranslator.validateScrollability();
}
@Override
protected boolean needsFetchingScroll() {
return queryTranslator.containsCollectionFetches();
}
@Override
public Loadable[] getEntityPersisters() {
return entityPersisters;
}
@Override
public String[] getAliases() {
return sqlAliases;
}
public String[] getSqlAliasSuffixes() {
return sqlAliasSuffixes;
}
@Override
public String[] getSuffixes() {
return getSqlAliasSuffixes();
}
@Override
public String[] getCollectionSuffixes() {
return collectionSuffixes;
}
@Override
protected String getQueryIdentifier() {
return queryTranslator.getQueryIdentifier();
}
@Override
public String getSQLString() {
return queryTranslator.getSQLString();
}
@Override
protected CollectionPersister[] getCollectionPersisters() {
return collectionPersisters;
}
@Override
protected int[] getCollectionOwners() {
return collectionOwners;
}
@Override
protected boolean[] getEntityEagerPropertyFetches() {
return entityEagerPropertyFetches;
}
@Override
protected int[] getOwners() {
return owners;
}
@Override
protected EntityType[] getOwnerAssociationTypes() {
return ownerAssociationTypes;
}
@Override
protected boolean isSubselectLoadingEnabled() {
return hasSubselectLoadableCollections();
}
@Override
protected LockMode[] getLockModes(LockOptions lockOptions) {
if ( lockOptions == null ) {
return defaultLockModes;
}
if ( lockOptions.getAliasLockCount() == 0
&& ( lockOptions.getLockMode() == null || LockMode.NONE.equals( lockOptions.getLockMode() ) ) ) {
return defaultLockModes;
}
LockMode[] lockModesArray = new LockMode[entityAliases.length];
for ( int i = 0; i < entityAliases.length; i++ ) {
LockMode lockMode = lockOptions.getEffectiveLockMode( entityAliases[i] );
if ( lockMode == null ) {
lockMode = LockMode.NONE;
}
lockModesArray[i] = lockMode;
}
return lockModesArray;
}
@Override
protected String applyLocks(
String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws QueryException {
final LockOptions lockOptions = parameters.getLockOptions();
if ( lockOptions == null ||
( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) {
return sql;
}
if ( shouldUseFollowOnLocking( parameters, dialect, afterLoadActions ) ) {
return sql;
}
final LockOptions locks = new LockOptions( lockOptions.getLockMode() );
final Map<String, String[]> keyColumnNames = dialect.forUpdateOfColumns()
? new HashMap<>()
: null;
locks.setScope( lockOptions.getScope() );
locks.setTimeOut( lockOptions.getTimeOut() );
for ( Map.Entry<String, String> entry : sqlAliasByEntityAlias.entrySet() ) {
final String userAlias = entry.getKey();
final String drivingSqlAlias = entry.getValue();
if ( drivingSqlAlias == null ) {
throw new IllegalArgumentException( "could not locate alias to apply lock mode : " + userAlias );
}
final QueryNode select = (QueryNode) queryTranslator.getSqlAST();
final Lockable drivingPersister = (Lockable) select.getFromClause()
.findFromElementByUserOrSqlAlias( userAlias, drivingSqlAlias )
.getQueryable();
final String sqlAlias = drivingPersister.getRootTableAlias( drivingSqlAlias );
final LockMode effectiveLockMode = lockOptions.getEffectiveLockMode( userAlias );
locks.setAliasSpecificLockMode( sqlAlias, effectiveLockMode );
if ( keyColumnNames != null ) {
keyColumnNames.put( sqlAlias, drivingPersister.getRootTableIdentifierColumnNames() );
}
}
return dialect.applyLocksToSql( sql, locks, keyColumnNames );
}
@Override
protected void applyPostLoadLocks(Object[] row, LockMode[] lockModesArray, SharedSessionContractImplementor session) {
}
@Override
protected boolean upgradeLocks() {
return true;
}
private boolean hasSelectNew() {
return aggregatedSelectExpression != null && aggregatedSelectExpression.getResultTransformer() != null;
}
@Override
protected String[] getResultRowAliases() {
return queryReturnAliases;
}
@Override
protected ResultTransformer resolveResultTransformer(ResultTransformer resultTransformer) {
final ResultTransformer implicitResultTransformer = aggregatedSelectExpression == null
? null
: aggregatedSelectExpression.getResultTransformer();
return HolderInstantiator.resolveResultTransformer( implicitResultTransformer, resultTransformer );
}
@Override
protected boolean[] includeInResultRow() {
boolean[] includeInResultTuple = includeInSelect;
if ( hasScalars ) {
includeInResultTuple = new boolean[queryReturnTypes.length];
Arrays.fill( includeInResultTuple, true );
}
return includeInResultTuple;
}
@Override
protected Object getResultColumnOrRow(
Object[] row,
ResultTransformer transformer,
ResultSet rs,
SharedSessionContractImplementor session)
throws SQLException, HibernateException {
Object[] resultRow = getResultRow( row, rs, session );
boolean hasTransform = hasSelectNew() || transformer != null;
return ( !hasTransform && resultRow.length == 1 ?
resultRow[0] :
resultRow
);
}
@Override
protected Object[] getResultRow(Object[] row, ResultSet rs, SharedSessionContractImplementor session)
throws SQLException, HibernateException {
Object[] resultRow;
if ( hasScalars ) {
String[][] scalarColumns = scalarColumnNames;
int queryCols = queryReturnTypes.length;
resultRow = new Object[queryCols];
for ( int i = 0; i < queryCols; i++ ) {
resultRow[i] = queryReturnTypes[i].nullSafeGet( rs, scalarColumns[i], session, null );
}
}
else {
resultRow = toResultRow( row );
}
return resultRow;
}
@SuppressWarnings("unchecked")
@Override
protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException {
HolderInstantiator holderInstantiator = buildHolderInstantiator( resultTransformer );
if ( holderInstantiator.isRequired() ) {
for ( int i = 0; i < results.size(); i++ ) {
Object[] row = (Object[]) results.get( i );
Object result = holderInstantiator.instantiate( row );
results.set( i, result );
}
if ( !hasSelectNew() && resultTransformer != null ) {
return resultTransformer.transformList( results );
}
else {
return results;
}
}
else {
return results;
}
}
private HolderInstantiator buildHolderInstantiator(ResultTransformer queryLocalResultTransformer) {
final ResultTransformer implicitResultTransformer = aggregatedSelectExpression == null
? null
: aggregatedSelectExpression.getResultTransformer();
return HolderInstantiator.getHolderInstantiator(
implicitResultTransformer,
queryLocalResultTransformer,
queryReturnAliases
);
}
public List list(
SharedSessionContractImplementor session,
QueryParameters queryParameters) throws HibernateException {
checkQuery( queryParameters );
return list( session, queryParameters, queryTranslator.getQuerySpaces(), queryReturnTypes );
}
private void checkQuery(QueryParameters queryParameters) {
if ( hasSelectNew() && queryParameters.getResultTransformer() != null ) {
throw new QueryException( "ResultTransformer is not allowed for 'select new' queries." );
}
}
public Iterator iterate(
QueryParameters queryParameters,
EventSource session) throws HibernateException {
checkQuery( queryParameters );
final boolean stats = session.getFactory().getStatistics().isStatisticsEnabled();
long startTime = 0;
if ( stats ) {
startTime = System.nanoTime();
}
try {
if ( queryParameters.isCallable() ) {
throw new QueryException( "iterate() not supported for callable statements" );
}
final SqlStatementWrapper wrapper = executeQueryStatement(
queryParameters,
false,
Collections.emptyList(),
session
);
final ResultSet rs = wrapper.getResultSet();
final PreparedStatement st = (PreparedStatement) wrapper.getStatement();
final Iterator result = new IteratorImpl(
rs,
st,
session,
queryParameters.isReadOnly( session ),
queryReturnTypes,
queryTranslator.getColumnNames(),
buildHolderInstantiator( queryParameters.getResultTransformer() )
);
if ( stats ) {
final long endTime = System.nanoTime();
final long milliseconds = TimeUnit.MILLISECONDS.convert( endTime - startTime, TimeUnit.NANOSECONDS );
session.getFactory().getStatistics().queryExecuted(
getQueryIdentifier(),
0,
milliseconds
);
}
return result;
}
catch (SQLException sqle) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
sqle,
"could not execute query using iterate",
getSQLString()
);
}
}
public ScrollableResultsImplementor scroll(
final QueryParameters queryParameters,
final SharedSessionContractImplementor session) throws HibernateException {
checkQuery( queryParameters );
return scroll(
queryParameters,
queryReturnTypes,
buildHolderInstantiator( queryParameters.getResultTransformer() ),
session
);
}
private Object[] toResultRow(Object[] row) {
if ( selectLength == row.length ) {
return row;
}
else {
Object[] result = new Object[selectLength];
int j = 0;
for ( int i = 0; i < row.length; i++ ) {
if ( includeInSelect[i] ) {
result[j++] = row[i];
}
}
return result;
}
}
@Override
public int[] getNamedParameterLocs(String name) throws QueryException {
ParameterInformation info = queryTranslator.getParameterTranslations().getNamedParameterInformation( name );
if ( info == null ) {
try {
info = queryTranslator.getParameterTranslations().getPositionalParameterInformation(
Integer.parseInt( name )
);
}
catch (Exception ignore) {
}
}
if ( info == null ) {
throw new QueryException( "Unrecognized parameter label : " + name );
}
return info.getSourceLocations();
}
@Override
protected int bindParameterValues(
final PreparedStatement statement,
final QueryParameters queryParameters,
final int startIndex,
final SharedSessionContractImplementor session) throws SQLException {
int position = startIndex;
List<ParameterSpecification> parameterSpecs = queryTranslator.getCollectedParameterSpecifications();
for ( ParameterSpecification spec : parameterSpecs ) {
position += spec.bind( statement, queryParameters, session, position );
}
return position - startIndex;
}
}