com.blazebit.persistence.integration.hibernate.base.HibernateExtendedQuerySupport Maven / Gradle / Ivy
The newest version!
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Blazebit
*/
package com.blazebit.persistence.integration.hibernate.base;
import com.blazebit.apt.service.ServiceProvider;
import com.blazebit.persistence.ConfigurationProperties;
import com.blazebit.persistence.ReturningResult;
import com.blazebit.persistence.spi.ConfigurationSource;
import com.blazebit.persistence.spi.DbmsDialect;
import com.blazebit.persistence.spi.ExtendedQuerySupport;
import com.blazebit.reflection.ReflectionUtils;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.Query;
import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement;
import jakarta.persistence.criteria.CompoundSelection;
import org.hibernate.HibernateException;
import org.hibernate.NonUniqueResultException;
import org.hibernate.ScrollMode;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.internal.FilterJdbcParameter;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.TupleTransformer;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.internal.ScrollableResultsIterator;
import org.hibernate.query.spi.DomainQueryExecutionContext;
import org.hibernate.query.spi.Limit;
import org.hibernate.query.spi.NonSelectQueryPlan;
import org.hibernate.query.spi.QueryInterpretationCache;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.query.spi.QueryPlan;
import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.MultiTableDeleteQueryPlan;
import org.hibernate.query.sqm.internal.MultiTableUpdateQueryPlan;
import org.hibernate.query.sqm.internal.QuerySqmImpl;
import org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan;
import org.hibernate.query.sqm.internal.SimpleUpdateQueryPlan;
import org.hibernate.query.sqm.internal.SqmInterpretationsKey;
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
import org.hibernate.query.sqm.sql.SqmTranslation;
import org.hibernate.query.sqm.sql.SqmTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation;
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.from.CollectionTableGroup;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcOperationQuery;
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl;
import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl;
import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter;
import org.hibernate.sql.results.internal.TupleMetadata;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.sql.results.spi.RowTransformer;
import org.hibernate.type.Type;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* @author Christian Beikov
* @since 1.6.7
*/
@ServiceProvider(ExtendedQuerySupport.class)
public class HibernateExtendedQuerySupport implements ExtendedQuerySupport {
private static final Logger LOG = Logger.getLogger(HibernateExtendedQuerySupport.class.getName());
private static final Constructor TUPLE_METADATA_CONSTRUCTOR_62;
private static final Constructor TUPLE_METADATA_CONSTRUCTOR_63;
private static final RowTransformer ROW_TRANSFORMER_SINGULAR_RETURN;
private static final RowTransformer ROW_TRANSFORMER_STANDARD;
static {
Constructor constructor62 = null;
Constructor constructor63 = null;
try {
constructor63 = TupleMetadata.class.getConstructor(TupleElement[].class, String[].class);
} catch (NoSuchMethodException ex1) {
try {
constructor62 = TupleMetadata.class.getConstructor(Map.class);
} catch (NoSuchMethodException ex2) {
// ignore
}
}
if (constructor62 == null && constructor63 == null) {
throw new RuntimeException("Could not find constructor for TupleMetadata. Please report your version of hibernate so we can provide support for it!");
}
TUPLE_METADATA_CONSTRUCTOR_62 = constructor62;
TUPLE_METADATA_CONSTRUCTOR_63 = constructor63;
try {
ROW_TRANSFORMER_SINGULAR_RETURN = (RowTransformer) RowTransformerSingularReturnImpl.class.getField("INSTANCE" ).get(null);
ROW_TRANSFORMER_STANDARD = (RowTransformer) RowTransformerStandardImpl.class.getField("INSTANCE").get(null);
} catch (NoSuchFieldException | IllegalAccessException ex) {
throw new RuntimeException("Could not find standard row transformers. Please report your version of hibernate so we can provide support for it!", ex);
}
}
private final HibernateAccess hibernateAccess;
private final BoundedConcurrentHashMap participatingInterpretationCache;
private final BoundedConcurrentHashMap queryPlanCache;
public HibernateExtendedQuerySupport() {
Iterator serviceIter = ServiceLoader.load(HibernateAccess.class).iterator();
if (!serviceIter.hasNext()) {
throw new IllegalStateException("Hibernate integration was not found on the class path!");
}
this.hibernateAccess = serviceIter.next();
this.participatingInterpretationCache = new BoundedConcurrentHashMap<>(2048, 20, BoundedConcurrentHashMap.Eviction.LIRS);
this.queryPlanCache = new BoundedConcurrentHashMap<>(2048, 20, BoundedConcurrentHashMap.Eviction.LIRS);
}
@Override
public boolean supportsAdvancedSql() {
return true;
}
@Override
public boolean needsExampleQueryForAdvancedDml() {
return true;
}
@Override
public boolean applyFirstResultMaxResults(Query query, int firstResult, int maxResults) {
Limit limit = query.unwrap(QuerySqmImpl.class).getQueryOptions().getLimit();
Integer firstRow = firstResult == 0 ? null : firstResult;
Integer maxRows = maxResults == Integer.MAX_VALUE ? null : maxResults;
boolean changed = firstRow == null && limit.getFirstRow() != null || firstRow != null && limit.getFirstRow() == null
|| maxRows == null && limit.getMaxRows() != null || maxRows != null && limit.getMaxRows() == null;
limit.setFirstRow(firstRow);
limit.setMaxRows(maxRows);
return changed;
}
@Override
public String getSql(EntityManager em, Query query) {
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SessionFactoryImplementor factory = hqlQuery.getSessionFactory();
CacheableSqmInterpretation interpretation = buildQueryPlan(query);
try {
return getJdbcOperation(factory, interpretation, hqlQuery).getSqlString();
} finally {
interpretation.domainParameterXref.clearExpansions();
}
}
@Override
public boolean getSqlContainsLimit() {
return true;
}
@Override
public List getCascadingDeleteSql(EntityManager em, Query query) {
SessionFactoryImplementor sfi = em.unwrap(SessionImplementor.class).getSessionFactory();
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
if (hqlQuery.getSqmStatement() instanceof SqmDeleteStatement>) {
SqmDeleteStatement> deleteStatement = (SqmDeleteStatement>) hqlQuery.getSqmStatement();
String mutatingEntityName = deleteStatement.getTarget().getModel().getHibernateEntityName();
EntityMappingType entityDescriptor = sfi.getMappingMetamodel().getEntityDescriptor(mutatingEntityName);
SqlAstTranslatorFactory sqlAstTranslatorFactory = sfi.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
List deleteSqls = new ArrayList<>();
entityDescriptor.visitConstraintOrderedTables(
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
// final TableReference targetTableReference = new TableReference(
// tableExpression,
// null,
// false,
// sfi
// );
final Predicate matchingIdsPredicate = null;//new InSubQueryPredicate();
// matchingIdsPredicateProducer.produceRestriction(
// ids,
// entityDescriptor,
// targetTableReference,
// tableKeyColumnsVisitationSupplier,
// query
// );
// final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( sfi );
// final JdbcDelete jdbcOperation = sqlAstTranslator.translate( new DeleteStatement( targetTableReference, matchingIdsPredicate ) );
}
);
return deleteSqls;
}
return Collections.EMPTY_LIST;
}
@Override
public String getSqlAlias(EntityManager em, Query query, String alias, int queryPartNumber) {
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SqmQuerySpec> querySpec;
if (hqlQuery.getSqmStatement() instanceof SqmSelectStatement>) {
querySpec = getQuerySpec(((SqmSelectStatement>) hqlQuery.getSqmStatement()).getQueryPart(), queryPartNumber);
} else if (hqlQuery.getSqmStatement() instanceof SqmInsertSelectStatement>) {
querySpec = getQuerySpec(((SqmInsertSelectStatement>) hqlQuery.getSqmStatement()).getSelectQueryPart(), queryPartNumber);
} else {
throw new IllegalArgumentException("The alias " + alias + " could not be found in the query: " + query);
}
NavigablePath navigablePath = findNavigablePath(alias, querySpec);
CacheableSqmInterpretation interpretation = buildQuerySpecPlan(query);
TableGroup tableGroup = getTableGroup(interpretation, navigablePath);
return tableGroup.getPrimaryTableReference().getIdentificationVariable();
}
@Override
public SqlFromInfo getSqlFromInfo(EntityManager em, Query query, String alias, int queryPartNumber) {
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SqmQuerySpec> querySpec;
if (hqlQuery.getSqmStatement() instanceof SqmSelectStatement>) {
querySpec = getQuerySpec(((SqmSelectStatement>) hqlQuery.getSqmStatement()).getQueryPart(), queryPartNumber);
} else if (hqlQuery.getSqmStatement() instanceof SqmInsertSelectStatement>) {
querySpec = getQuerySpec(((SqmInsertSelectStatement>) hqlQuery.getSqmStatement()).getSelectQueryPart(), queryPartNumber);
} else {
throw new IllegalArgumentException("The alias " + alias + " could not be found in the query: " + query);
}
NavigablePath navigablePath = findNavigablePath(alias, querySpec);
CacheableSqmInterpretation interpretation = buildQuerySpecPlan(query);
TableGroup tableGroup = getTableGroup(interpretation, navigablePath);
NamedTableReference primaryTableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
String tableAlias = primaryTableReference.getIdentificationVariable();
SessionFactoryImplementor sfi = em.unwrap(SessionImplementor.class).getSessionFactory();
String fromText = primaryTableReference.getTableId() + " " + tableAlias;
String fakeFromText = primaryTableReference.getTableId() + "/**/ " + tableAlias;
// We introduce a special marker into the table name to be able to find the correct table reference
// Note that it is important, this interpretation does not come from a cache,
// otherwise bad things will happen due to this mutation
primaryTableReference.setPrunedTableExpression(primaryTableReference.getTableId() + "/**/");
String sql;
try {
sql = getJdbcOperation(sfi, interpretation, hqlQuery).getSqlString();
} finally {
interpretation.domainParameterXref.clearExpansions();
}
int startIndex = sql.indexOf(fakeFromText);
int endIndex = startIndex + fromText.length();
return new SqlFromInfo() {
@Override
public String getAlias() {
return tableAlias;
}
@Override
public int getFromStartIndex() {
return startIndex;
}
@Override
public int getFromEndIndex() {
return endIndex;
}
};
}
private NavigablePath findNavigablePath(String alias, SqmQuerySpec> querySpec) {
for (SqmRoot> root : querySpec.getFromClause().getRoots()) {
NavigablePath path = findNavigablePath(alias, root);
if (path != null) {
return path;
}
}
return null;
}
private NavigablePath findNavigablePath(String alias, SqmFrom, ?> sqmFrom) {
if (alias.equals(sqmFrom.getExplicitAlias())) {
return sqmFrom.getNavigablePath();
}
for (SqmJoin, ?> sqmJoin : sqmFrom.getSqmJoins()) {
NavigablePath path = findNavigablePath(alias, sqmJoin);
if (path != null) {
return path;
}
}
return null;
}
private SqmQuerySpec> getQuerySpec(SqmQueryPart> queryPart, int queryPartNumber) {
Object querySpec = getQuerySpec(queryPart, 0, queryPartNumber);
if (querySpec instanceof SqmQuerySpec>) {
return (SqmQuerySpec>) querySpec;
}
throw new IllegalArgumentException("Couldn't find query part number " + queryPartNumber + " in query part: " + queryPart);
}
private Object getQuerySpec(SqmQueryPart> queryPart, int currentNumber, int queryPartNumber) {
if (currentNumber == queryPartNumber) {
return queryPart.getFirstQuerySpec();
}
if (queryPart instanceof SqmQueryGroup>) {
List extends SqmQueryPart>> queryParts = ((SqmQueryGroup>) queryPart).getQueryParts();
int offset = 0;
for (int i = 0; i < queryParts.size(); i++) {
Object result = getQuerySpec(queryParts.get(i), currentNumber + offset, queryPartNumber);
if (result instanceof SqmQuerySpec>) {
return result;
}
offset += (int) result;
}
return offset;
}
return 1;
}
private TableGroup getTableGroup(CacheableSqmInterpretation interpretation, NavigablePath navigablePath) {
TableGroup tableGroup = interpretation.tableGroupAccess.findTableGroup(navigablePath);
if (tableGroup == null) {
Statement sqlAst = interpretation.sqmTranslation.getSqlAst();
if (sqlAst instanceof SelectStatement) {
tableGroup = findTableGroup(((SelectStatement) sqlAst).getQueryPart(), navigablePath);
} else if (sqlAst instanceof InsertSelectStatement) {
tableGroup = findTableGroup(((InsertSelectStatement) sqlAst).getSourceSelectStatement(), navigablePath);
} else {
tableGroup = null;
}
}
if (tableGroup != null) {
if (tableGroup instanceof CollectionTableGroup) {
TableGroup elementTableGroup = ((CollectionTableGroup) tableGroup).getElementTableGroup();
return elementTableGroup == null || elementTableGroup instanceof LazyTableGroup && ((LazyTableGroup) elementTableGroup).getUnderlyingTableGroup() == null ? tableGroup : elementTableGroup;
}
return tableGroup;
}
throw new IllegalArgumentException("Couldn't find the table group for the navigable path: " + navigablePath);
}
private TableGroup findTableGroup(QueryPart queryPart, NavigablePath navigablePath) {
if (queryPart instanceof QueryGroup) {
for (QueryPart part : ((QueryGroup) queryPart).getQueryParts()) {
TableGroup tableGroup = findTableGroup(part, navigablePath);
if (tableGroup != null) {
return tableGroup;
}
}
} else {
QuerySpec querySpec = (QuerySpec) queryPart;
return querySpec.getFromClause().queryTableGroups(tableGroup -> tableGroup.getNavigablePath() == navigablePath ? tableGroup : null);
}
return null;
}
@Override
public int getSqlSelectAliasPosition(EntityManager em, Query query, String alias) {
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SqmQuerySpec> querySpec;
if (hqlQuery.getSqmStatement() instanceof SqmSelectStatement>) {
querySpec = ((SqmSelectStatement>) hqlQuery.getSqmStatement()).getQuerySpec();
} else if (hqlQuery.getSqmStatement() instanceof SqmInsertSelectStatement>) {
querySpec = ((SqmInsertSelectStatement>) hqlQuery.getSqmStatement()).getSelectQueryPart().getFirstQuerySpec();
} else {
throw new IllegalArgumentException("The alias " + alias + " could not be found in the query: " + query);
}
boolean found = false;
int position = 1;
for (SqmSelectableNode> selectionItem : querySpec.getSelectClause().getSelectionItems()) {
if (alias.equals(selectionItem.getAlias())) {
found = true;
break;
}
position++;
}
return found ? position : -1;
}
@Override
public int getSqlSelectAttributePosition(EntityManager em, Query query, String expression) {
if (expression.contains(".")) {
// TODO: implement
throw new UnsupportedOperationException("Embeddables are not yet supported!");
}
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SqmQuerySpec> querySpec;
if (hqlQuery.getSqmStatement() instanceof SqmSelectStatement>) {
querySpec = ((SqmSelectStatement>) hqlQuery.getSqmStatement()).getQuerySpec();
} else if (hqlQuery.getSqmStatement() instanceof SqmInsertSelectStatement>) {
querySpec = ((SqmInsertSelectStatement>) hqlQuery.getSqmStatement()).getSelectQueryPart().getFirstQuerySpec();
} else {
throw new IllegalArgumentException("The expression " + expression + " could not be found in the query: " + query);
}
boolean found = false;
int position = 1;
if (querySpec.getSelectClause().getSelectionItems().size() == 1 && querySpec.getSelectClause().getSelectionItems().get(0) instanceof SqmRoot>) {
SqmRoot> root = (SqmRoot>) querySpec.getSelectClause().getSelectionItems().get(0);
EntityPersister entityPersister = hqlQuery.getSessionFactory().getMetamodel().getEntityDescriptor(root.getEntityName());
int propertyIndex = entityPersister.getEntityMetamodel().getPropertyIndex(expression);
Type[] propertyTypes = entityPersister.getPropertyTypes();
for (int j = 0; j < propertyIndex; j++) {
position += propertyTypes[j].getColumnSpan(hqlQuery.getSessionFactory());
}
return position;
}
for (SqmSelectableNode> selectionItem : querySpec.getSelectClause().getSelectionItems()) {
if (expression.equals(selectionItem.asLoggableText())) {
found = true;
break;
}
position++;
}
return found ? position : -1;
}
@Override
@SuppressWarnings("rawtypes")
public List getResultList(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride, boolean queryPlanCacheEnabled) {
return getResultList(serviceProvider, participatingQueries, query, sqlOverride, queryPlanCacheEnabled, query.unwrap(DomainQueryExecutionContext.class));
}
private List getResultList(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride, boolean queryPlanCacheEnabled, DomainQueryExecutionContext executionContext) {
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SessionFactoryImplementor sessionFactory = hqlQuery.getSessionFactory();
RowTransformer> rowTransformer = determineRowTransformer((SqmSelectStatement>) hqlQuery.getSqmStatement(), hqlQuery.getResultType(), hqlQuery.getQueryOptions());
final SharedSessionContractImplementor session = hqlQuery.getSession();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
List parameterBinders = new ArrayList<>();
Set affectedTableNames = new HashSet<>();
Set filterJdbcParameters = new HashSet<>();
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(0);
for (Query participatingQuery : participatingQueries) {
CacheableSqmInterpretation interpretation = buildQueryPlan(participatingQuery);
JdbcOperationQuery jdbcOperation = getJdbcOperation(sessionFactory, interpretation, participatingQuery.unwrap(QuerySqmImpl.class));
if (query == participatingQuery) {
// Don't copy over the limit and offset parameters because we need to use the LimitHandler for now
JdbcOperationQuerySelect select = (JdbcOperationQuerySelect) jdbcOperation;
for (JdbcParameterBinder parameterBinder : jdbcOperation.getParameterBinders()) {
if (parameterBinder != select.getLimitParameter() && parameterBinder != select.getOffsetParameter()) {
parameterBinders.add(parameterBinder);
}
}
} else {
parameterBinders.addAll(jdbcOperation.getParameterBinders());
}
affectedTableNames.addAll(jdbcOperation.getAffectedTableNames());
filterJdbcParameters.addAll(jdbcOperation.getFilterJdbcParameters());
final Map, Map, List>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
interpretation.domainParameterXref,
interpretation.getSqmTranslation()::getJdbcParamsBySqmParam
);
final JdbcParameterBindings tempJdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
participatingQuery.unwrap(QuerySqmImpl.class).getQueryParameterBindings(),
interpretation.domainParameterXref,
jdbcParamsXref,
session.getFactory().getRuntimeMetamodels().getMappingMetamodel(),
interpretation.tableGroupAccess::findTableGroup,
new SqmParameterMappingModelResolutionAccess() {
@Override
@SuppressWarnings("unchecked")
public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) {
return (MappingModelExpressible) interpretation.sqmTranslation.getSqmParameterMappingModelTypeResolutions().get(parameter);
}
},
session
);
if (!tempJdbcParameterBindings.getBindings().isEmpty()) {
tempJdbcParameterBindings.visitBindings(jdbcParameterBindings::addBinding);
}
}
// todo: avoid double translation
CacheableSqmInterpretation interpretation = buildQueryPlan(query);
final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator(sessionFactory, (SelectStatement) interpretation.getSqmTranslation().getSqlAst())
.translate(jdbcParameterBindings, executionContext.getQueryOptions());
final JdbcOperationQuerySelect realJdbcSelect = new JdbcOperationQuerySelect(
sqlOverride,
parameterBinders,
jdbcSelect.getJdbcValuesMappingProducer(),
affectedTableNames,
filterJdbcParameters
// ,jdbcSelect.getRowsToSkip(),
// jdbcSelect.getMaxRows(),
// jdbcSelect.getAppliedParameters(),
// jdbcSelect.getLockStrategy(),
// jdbcSelect.getOffsetParameter(),
// jdbcSelect.getLimitParameter()
);
// todo: to get subselect fetching work, we need a slight API change in Hibernate because we need to inject our sql override somehow
// final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler(
// session.getPersistenceContext().getBatchFetchQueue(),
// sqmInterpretation.selectStatement,
// Collections.emptyList(),
// jdbcParameterBindings
// );
session.autoFlushIfRequired(realJdbcSelect.getAffectedTableNames());
try {
return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
realJdbcSelect,
jdbcParameterBindings,
new SqmJdbcExecutionContextAdapter(executionContext, jdbcSelect) {
@Override
public String getQueryIdentifier(String sql) {
return sql;
}
@Override
public boolean hasQueryExecutionToBeAddedToStatistics() {
return true;
}
},
rowTransformer,
ListResultsConsumer.UniqueSemantic.FILTER
);
} catch (HibernateException e) {
LOG.severe("Could not execute the following SQL query: " + sqlOverride);
if (session.getFactory().getSessionFactoryOptions().isJpaBootstrap()) {
throw session.getExceptionConverter().convert(e);
} else {
throw e;
}
} finally {
interpretation.domainParameterXref.clearExpansions();
}
}
@Override
public Object getResultStream(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride, boolean queryPlanCacheEnabled) {
return getResultStream(serviceProvider, participatingQueries, query, sqlOverride, queryPlanCacheEnabled, query.unwrap(DomainQueryExecutionContext.class));
}
private Object getResultStream(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride, boolean queryPlanCacheEnabled, DomainQueryExecutionContext executionContext) {
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SessionFactoryImplementor sessionFactory = hqlQuery.getSessionFactory();
RowTransformer> rowTransformer = determineRowTransformer((SqmSelectStatement>) hqlQuery.getSqmStatement(), hqlQuery.getResultType(), hqlQuery.getQueryOptions());
final SharedSessionContractImplementor session = hqlQuery.getSession();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
List parameterBinders = new ArrayList<>();
Set affectedTableNames = new HashSet<>();
Set filterJdbcParameters = new HashSet<>();
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(0);
for (Query participatingQuery : participatingQueries) {
CacheableSqmInterpretation interpretation = buildQueryPlan(participatingQuery);
JdbcOperationQuery jdbcOperation = getJdbcOperation(sessionFactory, interpretation, participatingQuery.unwrap(QuerySqmImpl.class));
parameterBinders.addAll(jdbcOperation.getParameterBinders());
affectedTableNames.addAll(jdbcOperation.getAffectedTableNames());
filterJdbcParameters.addAll(jdbcOperation.getFilterJdbcParameters());
final Map, Map, List>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
interpretation.domainParameterXref,
interpretation.getSqmTranslation()::getJdbcParamsBySqmParam
);
final JdbcParameterBindings tempJdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
executionContext.getQueryParameterBindings(),
interpretation.domainParameterXref,
jdbcParamsXref,
session.getFactory().getRuntimeMetamodels().getMappingMetamodel(),
interpretation.tableGroupAccess::findTableGroup,
new SqmParameterMappingModelResolutionAccess() {
@Override
@SuppressWarnings("unchecked")
public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) {
return (MappingModelExpressible) interpretation.sqmTranslation.getSqmParameterMappingModelTypeResolutions().get(parameter);
}
},
session
);
if (!tempJdbcParameterBindings.getBindings().isEmpty()) {
tempJdbcParameterBindings.visitBindings(jdbcParameterBindings::addBinding);
}
}
CacheableSqmInterpretation interpretation = buildQueryPlan(query);
final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator(sessionFactory, (SelectStatement) interpretation.getSqmTranslation().getSqlAst())
.translate(jdbcParameterBindings, executionContext.getQueryOptions());
final JdbcOperationQuerySelect realJdbcSelect = new JdbcOperationQuerySelect(
sqlOverride,
parameterBinders,
jdbcSelect.getJdbcValuesMappingProducer(),
affectedTableNames,
filterJdbcParameters
);
session.autoFlushIfRequired(realJdbcSelect.getAffectedTableNames());
try {
ScrollableResultsImplementor> scrollableResults = session.getFactory().getJdbcServices().getJdbcSelectExecutor().scroll(
realJdbcSelect,
ScrollMode.FORWARD_ONLY,
jdbcParameterBindings,
new SqmJdbcExecutionContextAdapter(executionContext, realJdbcSelect),
rowTransformer
);
ScrollableResultsIterator iterator = new ScrollableResultsIterator<>(scrollableResults);
Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL);
Stream stream = StreamSupport.stream(spliterator, false);
return stream.onClose(scrollableResults::close);
} catch (HibernateException e) {
LOG.severe("Could not execute the following SQL query: " + sqlOverride);
if (session.getFactory().getSessionFactoryOptions().isJpaBootstrap()) {
throw session.getExceptionConverter().convert(e);
} else {
throw e;
}
} finally {
interpretation.domainParameterXref.clearExpansions();
}
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Object getSingleResult(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride, boolean queryPlanCacheEnabled) {
final List list = getResultList(serviceProvider, participatingQueries, query, sqlOverride, queryPlanCacheEnabled);
if (list.size() == 0) {
throw new NoResultException("No entity found for query");
}
return uniqueElement(list);
}
private static R uniqueElement(List list) throws NonUniqueResultException {
int size = list.size();
if (size == 0) {
return null;
}
R first = list.get(0);
for (int i = 1; i < size; i++) {
if (list.get(i) != first) {
throw new NonUniqueResultException(list.size());
}
}
return first;
}
private RowTransformer determineRowTransformer(
SqmSelectStatement> sqm,
Class resultType,
QueryOptions queryOptions) {
if (resultType == null || resultType.isArray()) {
if (queryOptions.getTupleTransformer() != null) {
return makeRowTransformerTupleTransformerAdapter(sqm, queryOptions);
} else {
return ROW_TRANSFORMER_STANDARD;
}
}
// NOTE : if we get here, a result-type of some kind (other than Object[].class) was specified
final List> selections = sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections();
if (Tuple.class.isAssignableFrom(resultType)) {
// resultType is Tuple..
if (queryOptions.getTupleTransformer() == null) {
return (RowTransformer) new RowTransformerJpaTupleImpl(buildTupleMetadata(selections));
}
throw new IllegalArgumentException(
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " +
queryOptions.getTupleTransformer()
);
}
// NOTE : if we get here we have a resultType of some kind
if (queryOptions.getTupleTransformer() != null) {
// aside from checking the type parameters for the given TupleTransformer
// there is not a decent way to verify that the TupleTransformer returns
// the same type. We rely on the API here and assume the best
return makeRowTransformerTupleTransformerAdapter(sqm, queryOptions);
} else if (selections.size() > 1) {
throw new IllegalQueryOperationException("Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)");
} else {
return ROW_TRANSFORMER_SINGULAR_RETURN;
}
}
private TupleMetadata buildTupleMetadata(List> selections) {
try {
if (TUPLE_METADATA_CONSTRUCTOR_63 != null) {
return TUPLE_METADATA_CONSTRUCTOR_63.newInstance(buildTupleElementArray(selections), buildTupleAliasArray(selections));
} else {
final Map, Integer> tupleElementMap;
if (selections.size() == 1 && selections.get(0).getSelectableNode() instanceof CompoundSelection>) {
final List extends JpaSelection>> selectionItems = selections.get(0)
.getSelectableNode()
.getSelectionItems();
tupleElementMap = new IdentityHashMap<>(selectionItems.size());
for (int i = 0; i < selectionItems.size(); i++) {
tupleElementMap.put(selectionItems.get(i), i);
}
} else {
tupleElementMap = new IdentityHashMap<>(selections.size());
for (int i = 0; i < selections.size(); i++) {
final SqmSelection> selection = selections.get(i);
tupleElementMap.put(selection.getSelectableNode(), i);
}
}
return TUPLE_METADATA_CONSTRUCTOR_62.newInstance(tupleElementMap);
}
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException("Could not construct TupleMetadata. Please report your version of hibernate so we can provide support for it!", e);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
throw new RuntimeException("Could not construct TupleMetadata.", e);
}
}
private static TupleElement>[] buildTupleElementArray(List> selections) {
if (selections.size() == 1) {
final SqmSelectableNode> selectableNode = selections.get(0).getSelectableNode();
if (selectableNode instanceof CompoundSelection>) {
final List extends JpaSelection>> selectionItems = selectableNode.getSelectionItems();
final TupleElement>[] elements = new TupleElement>[selectionItems.size()];
for (int i = 0; i < selectionItems.size(); i++) {
elements[i] = selectionItems.get(i);
}
return elements;
} else {
return new TupleElement>[]{selectableNode};
}
} else {
final TupleElement>[] elements = new TupleElement>[selections.size()];
for (int i = 0; i < selections.size(); i++) {
elements[i] = selections.get(i).getSelectableNode();
}
return elements;
}
}
private static String[] buildTupleAliasArray(List> selections) {
if (selections.size() == 1) {
final SqmSelectableNode> selectableNode = selections.get(0).getSelectableNode();
if (selectableNode instanceof CompoundSelection>) {
final List extends JpaSelection>> selectionItems = selectableNode.getSelectionItems();
final String[] elements = new String[selectionItems.size()];
for (int i = 0; i < selectionItems.size(); i++) {
elements[i] = selectionItems.get(i).getAlias();
}
return elements;
} else {
return new String[]{selectableNode.getAlias()};
}
} else {
final String[] elements = new String[selections.size()];
for (int i = 0; i < selections.size(); i++) {
elements[i] = selections.get(i).getAlias();
}
return elements;
}
}
private RowTransformer makeRowTransformerTupleTransformerAdapter(
SqmSelectStatement> sqm,
QueryOptions queryOptions) {
final List aliases = new ArrayList<>();
for (SqmSelection> sqmSelection : sqm.getQuerySpec().getSelectClause().getSelections()) {
// The row a tuple transformer gets to see only contains 1 element for a dynamic instantiation
if (sqmSelection.getSelectableNode() instanceof SqmDynamicInstantiation>) {
aliases.add(sqmSelection.getAlias());
} else {
sqmSelection.getSelectableNode().visitSubSelectableNodes( subSelection -> aliases.add(subSelection.getAlias()) );
}
}
@SuppressWarnings("unchecked")
TupleTransformer tupleTransformer = (TupleTransformer) queryOptions.getTupleTransformer();
return new RowTransformerTupleTransformerAdapter(
ArrayHelper.toStringArray(aliases),
tupleTransformer
);
}
@Override
public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query baseQuery, Query query, String finalSql, boolean queryPlanCacheEnabled) {
EntityManager em = serviceProvider.getService(EntityManager.class);
SessionImplementor session = em.unwrap(SessionImplementor.class);
if (session.isClosed()) {
throw new PersistenceException("Entity manager is closed!");
}
final SessionFactoryImplementor sessionFactory = session.getSessionFactory();
List parameterBinders = new ArrayList<>();
Set affectedTableNames = new HashSet<>();
Set filterJdbcParameters = new HashSet<>();
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl(0);
for (Query participatingQuery : participatingQueries) {
CacheableSqmInterpretation interpretation = buildQueryPlan(participatingQuery);
JdbcOperationQuery jdbcOperation = getJdbcOperation(sessionFactory, interpretation, participatingQuery.unwrap(QuerySqmImpl.class));
parameterBinders.addAll(jdbcOperation.getParameterBinders());
affectedTableNames.addAll(jdbcOperation.getAffectedTableNames());
filterJdbcParameters.addAll(jdbcOperation.getFilterJdbcParameters());
final Map, Map, List>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
interpretation.domainParameterXref,
interpretation.getSqmTranslation()::getJdbcParamsBySqmParam
);
final JdbcParameterBindings tempJdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
participatingQuery.unwrap(DomainQueryExecutionContext.class).getQueryParameterBindings(),
interpretation.domainParameterXref,
jdbcParamsXref,
session.getFactory().getRuntimeMetamodels().getMappingMetamodel(),
interpretation.tableGroupAccess::findTableGroup,
new SqmParameterMappingModelResolutionAccess() {
@Override
@SuppressWarnings("unchecked")
public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) {
return (MappingModelExpressible) interpretation.sqmTranslation.getSqmParameterMappingModelTypeResolutions().get(parameter);
}
},
session
);
if (!tempJdbcParameterBindings.getBindings().isEmpty()) {
tempJdbcParameterBindings.visitBindings(jdbcParameterBindings::addBinding);
}
}
QuerySqmImpl> hqlQuery = query.unwrap(QuerySqmImpl.class);
SqmStatement> sqmStatement = hqlQuery.getSqmStatement();
CacheableSqmInterpretation interpretation = buildQueryPlan(query);
final JdbcOperationQueryMutation realJdbcStatement;
if (sqmStatement instanceof SqmUpdateStatement>) {
// final JdbcUpdate jdbcUpdate = sqlAstTranslatorFactory.buildUpdateTranslator(sessionFactory, (UpdateStatement) interpretation.getSqmTranslation().getSqlAst())
// .translate(jdbcParameterBindings, executionContext.getQueryOptions());
realJdbcStatement = new JdbcOperationQueryUpdate(
finalSql,
parameterBinders,
affectedTableNames,
filterJdbcParameters,
Collections.emptyMap()
);
} else if (sqmStatement instanceof SqmDeleteStatement>) {
realJdbcStatement = new JdbcOperationQueryUpdate(
finalSql,
parameterBinders,
affectedTableNames,
filterJdbcParameters,
Collections.emptyMap()
);
} else if (sqmStatement instanceof SqmInsertSelectStatement>) {
realJdbcStatement = new JdbcOperationQueryUpdate(
finalSql,
parameterBinders,
affectedTableNames,
filterJdbcParameters,
Collections.emptyMap()
);
} else {
throw new IllegalArgumentException("Unsupported sqm statement: " + sqmStatement);
}
session.autoFlushIfRequired(realJdbcStatement.getAffectedTableNames());
Function statementCreator = sql -> session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql);
BiConsumer expectationCheck = (integer, preparedStatement) -> { };
try {
return session.getFactory().getJdbcServices().getJdbcMutationExecutor().execute(
realJdbcStatement,
jdbcParameterBindings,
statementCreator,
expectationCheck,
SqmJdbcExecutionContextAdapter.usingLockingAndPaging(query.unwrap(DomainQueryExecutionContext.class))
);
} catch (HibernateException e) {
LOG.severe("Could not execute the following SQL query: " + finalSql);
if (session.getFactory().getSessionFactoryOptions().isJpaBootstrap()) {
throw session.getExceptionConverter().convert(e);
} else {
throw e;
}
} finally {
interpretation.domainParameterXref.clearExpansions();
}
}
@Override
@SuppressWarnings("unchecked")
public ReturningResult
© 2015 - 2025 Weber Informatics LLC | Privacy Policy