/*
* Copyright 2014 - 2017 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.blazebit.persistence.impl;
import com.blazebit.persistence.CaseWhenStarterBuilder;
import com.blazebit.persistence.CriteriaBuilderFactory;
import com.blazebit.persistence.From;
import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.FullSelectCTECriteriaBuilder;
import com.blazebit.persistence.HavingOrBuilder;
import com.blazebit.persistence.JoinOnBuilder;
import com.blazebit.persistence.JoinType;
import com.blazebit.persistence.Keyset;
import com.blazebit.persistence.KeysetBuilder;
import com.blazebit.persistence.LeafOngoingFinalSetOperationCTECriteriaBuilder;
import com.blazebit.persistence.MultipleSubqueryInitiator;
import com.blazebit.persistence.RestrictionBuilder;
import com.blazebit.persistence.ReturningModificationCriteriaBuilderFactory;
import com.blazebit.persistence.SelectRecursiveCTECriteriaBuilder;
import com.blazebit.persistence.SimpleCaseWhenStarterBuilder;
import com.blazebit.persistence.StartOngoingSetOperationCTECriteriaBuilder;
import com.blazebit.persistence.SubqueryBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.WhereOrBuilder;
import com.blazebit.persistence.impl.expression.Expression;
import com.blazebit.persistence.impl.expression.ExpressionFactory;
import com.blazebit.persistence.impl.expression.PathExpression;
import com.blazebit.persistence.impl.expression.SubqueryExpressionFactory;
import com.blazebit.persistence.impl.expression.VisitorAdapter;
import com.blazebit.persistence.impl.function.entity.ValuesEntity;
import com.blazebit.persistence.impl.keyset.KeysetBuilderImpl;
import com.blazebit.persistence.impl.keyset.KeysetImpl;
import com.blazebit.persistence.impl.keyset.KeysetLink;
import com.blazebit.persistence.impl.keyset.KeysetManager;
import com.blazebit.persistence.impl.keyset.KeysetMode;
import com.blazebit.persistence.impl.keyset.SimpleKeysetLink;
import com.blazebit.persistence.impl.predicate.Predicate;
import com.blazebit.persistence.impl.query.AbstractCustomQuery;
import com.blazebit.persistence.impl.query.CTENode;
import com.blazebit.persistence.impl.query.CustomQuerySpecification;
import com.blazebit.persistence.impl.query.CustomSQLQuery;
import com.blazebit.persistence.impl.query.CustomSQLTypedQuery;
import com.blazebit.persistence.impl.query.DefaultQuerySpecification;
import com.blazebit.persistence.impl.query.EntityFunctionNode;
import com.blazebit.persistence.impl.query.ObjectBuilderTypedQuery;
import com.blazebit.persistence.impl.query.QuerySpecification;
import com.blazebit.persistence.impl.transform.ExpressionTransformerGroup;
import com.blazebit.persistence.impl.transform.OuterFunctionVisitor;
import com.blazebit.persistence.impl.transform.SimpleTransformerGroup;
import com.blazebit.persistence.impl.transform.SizeTransformationVisitor;
import com.blazebit.persistence.impl.transform.SizeTransformerGroup;
import com.blazebit.persistence.impl.transform.SubqueryRecursiveExpressionVisitor;
import com.blazebit.persistence.spi.ConfigurationSource;
import com.blazebit.persistence.spi.DbmsDialect;
import com.blazebit.persistence.spi.DbmsModificationState;
import com.blazebit.persistence.spi.DbmsStatementType;
import com.blazebit.persistence.spi.JpaProvider;
import com.blazebit.persistence.spi.JpqlFunction;
import com.blazebit.persistence.spi.JpqlMacro;
import com.blazebit.persistence.spi.ServiceProvider;
import com.blazebit.persistence.spi.SetOperationType;
import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
*
* @param <QueryResultType> The query result type
* @param <BuilderType> The concrete builder type
* @param <SetReturn> The builder type that should be returned on set operations
* @param <SubquerySetReturn> The builder type that should be returned on subquery set operations
* @author Christian Beikov
* @author Moritz Becker
* @since 1.0
*/
public abstract class AbstractCommonQueryBuilder<QueryResultType, BuilderType, SetReturn, SubquerySetReturn, FinalSetReturn extends BaseFinalSetOperationBuilderImpl<?, ?, ?>> implements ServiceProvider, ConfigurationSource {
public static final String ID_PARAM_NAME = "ids";
protected static final Logger LOG = Logger.getLogger(AbstractCommonQueryBuilder.class.getName());
protected final MainQuery mainQuery;
/* This might change when transitioning to a set operation */
protected boolean isMainQuery;
protected final CriteriaBuilderFactoryImpl cbf;
protected final EntityManager em;
protected final DbmsStatementType statementType;
protected final Map<Class<?>, Map<String, DbmsModificationState>> explicitVersionEntities = new HashMap<Class<?>, Map<String, DbmsModificationState>>(0);
protected final ParameterManager parameterManager;
protected final SelectManager<QueryResultType> selectManager;
protected final WhereManager<BuilderType> whereManager;
protected final HavingManager<BuilderType> havingManager;
protected final GroupByManager groupByManager;
protected final OrderByManager orderByManager;
protected final JoinManager joinManager;
protected final KeysetManager keysetManager;
protected final ResolvingQueryGenerator queryGenerator;
protected final SubqueryInitiatorFactory subqueryInitFactory;
protected final GroupByExpressionGatheringVisitor groupByExpressionGatheringVisitor;
// This builder will be passed in when using set operations
protected FinalSetReturn finalSetOperationBuilder;
protected boolean setOperationEnded;
protected final DbmsDialect dbmsDialect;
protected final JpaProvider jpaProvider;
protected final Map<String, JpqlFunction> registeredFunctions;
protected final AliasManager aliasManager;
protected final ExpressionFactory expressionFactory;
// Mutable state
protected Class<QueryResultType> resultType;
protected int firstResult = 0;
protected int maxResults = Integer.MAX_VALUE;
protected boolean fromClassExplicitelySet = false;
protected final List<ExpressionTransformerGroup<?>> transformerGroups;
// Cache
protected String cachedQueryString;
protected String cachedExternalQueryString;
protected boolean hasGroupBy = false;
protected boolean needsCheck = true;
// Fetch owner's are evaluated during implicit joining
protected Set<JoinNode> nodesToFetch;
private boolean checkSetBuilderEnded = true;
private boolean implicitJoinsApplied = false;
/**
* Create flat copy of builder
*
* @param builder
*/
@SuppressWarnings("unchecked")
protected AbstractCommonQueryBuilder(AbstractCommonQueryBuilder<QueryResultType, ?, ?, ?, ?> builder) {
this.mainQuery = builder.mainQuery;
this.isMainQuery = builder.isMainQuery;
this.cbf = builder.cbf;
this.statementType = builder.statementType;
this.orderByManager = builder.orderByManager;
this.parameterManager = builder.parameterManager;
this.selectManager = builder.selectManager;
this.whereManager = (WhereManager<BuilderType>) builder.whereManager;
this.havingManager = (HavingManager<BuilderType>) builder.havingManager;
this.groupByManager = builder.groupByManager;
this.keysetManager = builder.keysetManager;
this.joinManager = builder.joinManager;
this.queryGenerator = builder.queryGenerator;
this.em = builder.em;
this.finalSetOperationBuilder = (FinalSetReturn) builder.finalSetOperationBuilder;
this.dbmsDialect = builder.dbmsDialect;
this.jpaProvider = builder.jpaProvider;
this.registeredFunctions = builder.registeredFunctions;
this.subqueryInitFactory = builder.subqueryInitFactory;
this.groupByExpressionGatheringVisitor = builder.groupByExpressionGatheringVisitor;
this.aliasManager = builder.aliasManager;
this.expressionFactory = builder.expressionFactory;
this.transformerGroups = builder.transformerGroups;
this.resultType = builder.resultType;
}
protected AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class<QueryResultType> resultClazz, String alias, AliasManager aliasManager, JoinManager parentJoinManager, ExpressionFactory expressionFactory, FinalSetReturn finalSetOperationBuilder, boolean implicitFromClause) {
if (mainQuery == null) {
throw new NullPointerException("mainQuery");
}
if (statementType == null) {
throw new NullPointerException("statementType");
}
if (resultClazz == null) {
throw new NullPointerException("resultClazz");
}
this.mainQuery = mainQuery;
this.isMainQuery = isMainQuery;
this.statementType = statementType;
this.cbf = mainQuery.cbf;
this.parameterManager = mainQuery.parameterManager;
this.em = mainQuery.em;
this.dbmsDialect = mainQuery.dbmsDialect;
this.jpaProvider = mainQuery.jpaProvider;
this.registeredFunctions = mainQuery.registeredFunctions;
this.aliasManager = new AliasManager(aliasManager);
this.expressionFactory = expressionFactory;
this.queryGenerator = new ResolvingQueryGenerator(this.aliasManager, parameterManager, mainQuery.parameterTransformerFactory, mainQuery.metamodel, jpaProvider, registeredFunctions);
this.joinManager = new JoinManager(mainQuery, queryGenerator, this.aliasManager, parentJoinManager, expressionFactory);
if (implicitFromClause) {
// set defaults
if (alias != null) {
// If the user supplies an alias, the intention is clear
fromClassExplicitelySet = true;
}
EntityType<QueryResultType> type = mainQuery.metamodel.getEntity(resultClazz);
if (type == null) {
// the result class might not be an entity
if (fromClassExplicitelySet) {
// If the intention was to use that as from clause, we have to throw an exception
throw new IllegalArgumentException("The class [" + resultClazz.getName() + "] is not an entity and therefore can't be aliased!");
}
} else {
this.joinManager.addRoot(type, alias);
}
}
this.subqueryInitFactory = joinManager.getSubqueryInitFactory();
this.groupByExpressionGatheringVisitor = new GroupByExpressionGatheringVisitor(false, dbmsDialect);
this.whereManager = new WhereManager<BuilderType>(queryGenerator, parameterManager, subqueryInitFactory, expressionFactory);
this.havingManager = new HavingManager<BuilderType>(queryGenerator, parameterManager, subqueryInitFactory, expressionFactory, groupByExpressionGatheringVisitor);
this.groupByManager = new GroupByManager(queryGenerator, parameterManager, subqueryInitFactory);
this.selectManager = new SelectManager<QueryResultType>(queryGenerator, parameterManager, this.joinManager, this.aliasManager, subqueryInitFactory, expressionFactory, jpaProvider, mainQuery, groupByExpressionGatheringVisitor, resultClazz);
this.orderByManager = new OrderByManager(queryGenerator, parameterManager, subqueryInitFactory, this.aliasManager, jpaProvider, groupByExpressionGatheringVisitor);
this.keysetManager = new KeysetManager(queryGenerator, parameterManager);
final SizeTransformationVisitor sizeTransformationVisitor = new SizeTransformationVisitor(mainQuery, subqueryInitFactory, joinManager, jpaProvider);
this.transformerGroups = Arrays.<ExpressionTransformerGroup<?>>asList(
new SimpleTransformerGroup(new OuterFunctionVisitor(joinManager)),
new SimpleTransformerGroup(new SubqueryRecursiveExpressionVisitor()),
new SizeTransformerGroup(sizeTransformationVisitor, orderByManager, selectManager, joinManager, groupByManager));
this.resultType = resultClazz;
this.finalSetOperationBuilder = finalSetOperationBuilder;
}
public AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class<QueryResultType> resultClazz, String alias, FinalSetReturn finalSetOperationBuilder, boolean implicitFromClause) {
this(mainQuery, isMainQuery, statementType, resultClazz, alias, null, null, mainQuery.expressionFactory, finalSetOperationBuilder, implicitFromClause);
}
public AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class<QueryResultType> resultClazz, String alias, FinalSetReturn finalSetOperationBuilder) {
this(mainQuery, isMainQuery, statementType, resultClazz, alias, null, null, mainQuery.expressionFactory, finalSetOperationBuilder, true);
}
public AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class<QueryResultType> resultClazz, String alias) {
this(mainQuery, isMainQuery, statementType, resultClazz, alias, null);
}
public CriteriaBuilderFactory getCriteriaBuilderFactory() {
return cbf;
}
public DbmsStatementType getStatementType() {
return statementType;
}
@SuppressWarnings("unchecked")
public <T> T getService(Class<T> serviceClass) {
if (CriteriaBuilderFactory.class.equals(serviceClass)) {
return (T) cbf;
} else if (ConfigurationSource.class.equals(serviceClass)) {
return (T) this;
} else if (EntityManager.class.equals(serviceClass)) {
return (T) em;
} else if (DbmsDialect.class.equals(serviceClass)) {
return (T) dbmsDialect;
} else if (SubqueryExpressionFactory.class.equals(serviceClass)) {
return (T) mainQuery.subqueryExpressionFactory;
} else if (ExpressionFactory.class.equals(serviceClass)) {
return (T) mainQuery.expressionFactory;
} else if (JoinOnBuilder.class.equals(serviceClass)) {
// TODO: We should think of a better way to expose a where builder to clients as an on builder
// TODO: Setting the expression via this does not clear the cache
return (T) whereManager.startOnBuilder(this);
}
return cbf.getService(serviceClass);
}
@SuppressWarnings("unchecked")
public BuilderType registerMacro(String macroName, JpqlMacro jpqlMacro) {
prepareForModification();
this.mainQuery.registerMacro(macroName, jpqlMacro);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setProperty(String propertyName, String propertyValue) {
prepareForModification();
this.mainQuery.getMutableQueryConfiguration().setProperty(propertyName, propertyValue);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setProperties(Map<String, String> properties) {
prepareForModification();
this.mainQuery.getMutableQueryConfiguration().setProperties(properties);
return (BuilderType) this;
}
public Map<String, String> getProperties() {
return this.mainQuery.getQueryConfiguration().getProperties();
}
public String getProperty(String name) {
return this.mainQuery.getQueryConfiguration().getProperty(name);
}
@SuppressWarnings("unchecked")
public StartOngoingSetOperationCTECriteriaBuilder<BuilderType, LeafOngoingFinalSetOperationCTECriteriaBuilder<BuilderType>> withStartSet(Class<?> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
prepareForModification();
return mainQuery.cteManager.withStartSet(cteClass, (BuilderType) this);
}
@SuppressWarnings("unchecked")
public FullSelectCTECriteriaBuilder<BuilderType> with(Class<?> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
prepareForModification();
return mainQuery.cteManager.with(cteClass, (BuilderType) this);
}
@SuppressWarnings("unchecked")
public SelectRecursiveCTECriteriaBuilder<BuilderType> withRecursive(Class<?> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
prepareForModification();
return mainQuery.cteManager.withRecursive(cteClass, (BuilderType) this);
}
@SuppressWarnings("unchecked")
public ReturningModificationCriteriaBuilderFactory<BuilderType> withReturning(Class<?> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
if (!dbmsDialect.supportsModificationQueryInWithClause()) {
throw new UnsupportedOperationException("The database does not support modification queries in the with clause!");
}
prepareForModification();
return mainQuery.cteManager.withReturning(cteClass, (BuilderType) this);
}
public SetReturn union() {
return addSetOperation(SetOperationType.UNION);
}
public SetReturn unionAll() {
return addSetOperation(SetOperationType.UNION_ALL);
}
public SetReturn intersect() {
return addSetOperation(SetOperationType.INTERSECT);
}
public SetReturn intersectAll() {
return addSetOperation(SetOperationType.INTERSECT_ALL);
}
public SetReturn except() {
return addSetOperation(SetOperationType.EXCEPT);
}
public SetReturn exceptAll() {
return addSetOperation(SetOperationType.EXCEPT_ALL);
}
public SubquerySetReturn startUnion() {
return addSubquerySetOperation(SetOperationType.UNION);
}
public SubquerySetReturn startUnionAll() {
return addSubquerySetOperation(SetOperationType.UNION_ALL);
}
public SubquerySetReturn startIntersect() {
return addSubquerySetOperation(SetOperationType.INTERSECT);
}
public SubquerySetReturn startIntersectAll() {
return addSubquerySetOperation(SetOperationType.INTERSECT_ALL);
}
public SubquerySetReturn startExcept() {
return addSubquerySetOperation(SetOperationType.EXCEPT);
}
public SubquerySetReturn startExceptAll() {
return addSubquerySetOperation(SetOperationType.EXCEPT_ALL);
}
public SubquerySetReturn startSet() {
return addSubquerySetOperation(null);
}
private SetReturn addSetOperation(SetOperationType type) {
prepareForModification();
this.setOperationEnded = true;
// We only check non-empty queries since empty ones will be replaced
if (!isEmpty()) {
prepareAndCheck();
}
FinalSetReturn finalSetOperationBuilder = this.finalSetOperationBuilder;
if (finalSetOperationBuilder == null) {
finalSetOperationBuilder = createFinalSetOperationBuilder(type, false);
finalSetOperationBuilder.setOperationManager.setStartQueryBuilder(this);
this.finalSetOperationBuilder = finalSetOperationBuilder;
} else {
SetOperationManager oldOperationManager = finalSetOperationBuilder.setOperationManager;
if (oldOperationManager.getOperator() == null) {
oldOperationManager.setOperator(type);
} else if (oldOperationManager.getOperator() != type) {
// Put existing set operands into a sub builder and use the sub builder as new start
FinalSetReturn subFinalSetOperationBuilder = createFinalSetOperationBuilder(oldOperationManager.getOperator(), false);
subFinalSetOperationBuilder.setOperationManager.setStartQueryBuilder(oldOperationManager.getStartQueryBuilder());
subFinalSetOperationBuilder.setOperationManager.getSetOperations().addAll(oldOperationManager.getSetOperations());
oldOperationManager.setStartQueryBuilder(subFinalSetOperationBuilder);
oldOperationManager.getSetOperations().clear();
oldOperationManager.setOperator(type);
}
}
SetReturn setOperand = createSetOperand(finalSetOperationBuilder);
finalSetOperationBuilder.setOperationManager.addSetOperation((AbstractCommonQueryBuilder<?, ?, ?, ?, ?>) setOperand);
return setOperand;
}
private SubquerySetReturn addSubquerySetOperation(SetOperationType type) {
prepareForModification();
this.setOperationEnded = true;
// We only check non-empty queries since empty ones will be replaced
if (!isEmpty()) {
prepareAndCheck();
}
FinalSetReturn parentFinalSetOperationBuilder = this.finalSetOperationBuilder;
if (parentFinalSetOperationBuilder == null) {
parentFinalSetOperationBuilder = createFinalSetOperationBuilder(type, false);
parentFinalSetOperationBuilder.setOperationManager.setStartQueryBuilder(this);
this.finalSetOperationBuilder = parentFinalSetOperationBuilder;
this.needsCheck = true;
} else {
SetOperationManager oldParentOperationManager = finalSetOperationBuilder.setOperationManager;
if (oldParentOperationManager.getOperator() == null) {
oldParentOperationManager.setOperator(type);
} else if (oldParentOperationManager.getOperator() != type) {
// Put existing set operands into a sub builder and use the sub builder as new start
FinalSetReturn subFinalSetOperationBuilder = createFinalSetOperationBuilder(oldParentOperationManager.getOperator(), false);
subFinalSetOperationBuilder.setOperationManager.setStartQueryBuilder(oldParentOperationManager.getStartQueryBuilder());
subFinalSetOperationBuilder.setOperationManager.getSetOperations().addAll(oldParentOperationManager.getSetOperations());
oldParentOperationManager.setStartQueryBuilder(subFinalSetOperationBuilder);
oldParentOperationManager.getSetOperations().clear();
oldParentOperationManager.setOperator(type);
}
}
FinalSetReturn finalSetOperationBuilder = createFinalSetOperationBuilder(type, true);
SubquerySetReturn subquerySetOperand = createSubquerySetOperand(finalSetOperationBuilder, parentFinalSetOperationBuilder);
finalSetOperationBuilder.setOperationManager.setStartQueryBuilder((AbstractCommonQueryBuilder<?, ?, ?, ?, ?>) subquerySetOperand);
if (type != null) {
parentFinalSetOperationBuilder.setOperationManager.addSetOperation(finalSetOperationBuilder);
} else {
parentFinalSetOperationBuilder.setOperationManager.setStartQueryBuilder(finalSetOperationBuilder);
}
return subquerySetOperand;
}
protected FinalSetReturn createFinalSetOperationBuilder(SetOperationType operator, boolean nested) {
throw new IllegalArgumentException("Set operations aren't supported!");
}
protected SetReturn createSetOperand(FinalSetReturn baseQueryBuilder) {
throw new IllegalArgumentException("Set operations aren't supported!");
}
protected SubquerySetReturn createSubquerySetOperand(FinalSetReturn baseQueryBuilder, FinalSetReturn resultFinalSetOperationBuilder) {
throw new IllegalArgumentException("Set operations aren't supported!");
}
public BuilderType from(String correlationPath) {
return from(correlationPath, null);
}
public BuilderType from(String correlationPath, String alias) {
if (!(this instanceof SubqueryBuilder<?>)) {
throw new IllegalStateException("Cannot use a correlation path in a non-subquery!");
}
prepareForModification();
joinManager.addRoot(correlationPath, alias);
return (BuilderType) this;
}
public BuilderType from(Class<?> clazz) {
return from(clazz, null);
}
public BuilderType from(Class<?> clazz, String alias) {
return from(clazz, alias, null);
}
public BuilderType fromCte(Class<?> clazz, String cteName) {
return fromCte(clazz, null);
}
public BuilderType fromCte(Class<?> clazz, String cteName, String alias) {
return from(clazz, alias, null);
}
public BuilderType fromOld(Class<?> clazz) {
return fromOld(clazz, null);
}
public BuilderType fromOld(Class<?> clazz, String alias) {
return from(clazz, alias, DbmsModificationState.OLD);
}
public BuilderType fromNew(Class<?> clazz) {
return fromNew(clazz, null);
}
public BuilderType fromNew(Class<?> clazz, String alias) {
return from(clazz, alias, DbmsModificationState.NEW);
}
public <T> BuilderType fromValues(Class<T> valueClass, String alias, Collection<T> values) {
BuilderType result = fromValues(valueClass, alias, values.size());
setParameter(alias, values);
return result;
}
public <T> BuilderType fromIdentifiableValues(Class<T> valueClass, String alias, Collection<T> values) {
BuilderType result = fromIdentifiableValues(valueClass, alias, values.size());
setParameter(alias, values);
return result;
}
public BuilderType fromIdentifiableValues(Class<?> valueClass, String alias, int valueCount) {
prepareForModification();
if (!fromClassExplicitelySet) {
// When from is explicitly called we have to revert the implicit root
if (joinManager.getRoots().size() > 0) {
joinManager.removeRoot();
}
}
Class<?> valuesClazz = valueClass;
ManagedType<?> type = mainQuery.metamodel.getManagedType(valueClass);
String treatFunction = null;
String castedParameter = null;
if (!(type instanceof IdentifiableType<?>)) {
throw new IllegalArgumentException("Only identifiable types allowed!");
}
joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, treatFunction, castedParameter, true);
fromClassExplicitelySet = true;
return (BuilderType) this;
}
public BuilderType fromValues(Class<?> valueClass, String alias, int valueCount) {
prepareForModification();
if (!fromClassExplicitelySet) {
// When from is explicitly called we have to revert the implicit root
if (joinManager.getRoots().size() > 0) {
joinManager.removeRoot();
}
}
Class<?> valuesClazz = valueClass;
ManagedType<?> type = mainQuery.metamodel.getManagedType(valueClass);
String treatFunction = null;
String castedParameter = null;
if (type == null) {
treatFunction = cbf.getTreatFunctions().get(valueClass);
if (treatFunction == null) {
throw new IllegalArgumentException("Unsupported non-managed type for VALUES clause: " + valueClass.getName());
}
String sqlType = mainQuery.dbmsDialect.getSqlType(valueClass);
castedParameter = mainQuery.dbmsDialect.cast("?", sqlType);
valuesClazz = ValuesEntity.class;
}
joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, treatFunction, castedParameter, false);
fromClassExplicitelySet = true;
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
private BuilderType from(Class<?> clazz, String alias, DbmsModificationState state) {
prepareForModification();
if (!fromClassExplicitelySet) {
// When from is explicitly called we have to revert the implicit root
if (joinManager.getRoots().size() > 0) {
joinManager.removeRoot();
}
}
EntityType<?> type = mainQuery.metamodel.entity(clazz);
String finalAlias = joinManager.addRoot(type, alias);
fromClassExplicitelySet = true;
// Handle old and new references
if (state != null) {
Map<String, DbmsModificationState> versionEntities = explicitVersionEntities.get(clazz);
if (versionEntities == null) {
versionEntities = new HashMap<String, DbmsModificationState>(1);
explicitVersionEntities.put(clazz, versionEntities);
}
versionEntities.put(finalAlias, state);
}
return (BuilderType) this;
}
public Set<From> getRoots() {
return new LinkedHashSet<From>(joinManager.getRoots());
}
public JoinNode getRoot() {
return joinManager.getRootNodeOrFail("This should never happen. Please report this error!");
}
public JoinNode getFrom(String alias) {
AliasInfo info = aliasManager.getAliasInfo(alias);
if (info == null || !(info instanceof JoinAliasInfo)) {
return null;
}
return ((JoinAliasInfo) info).getJoinNode();
}
public JoinNode getFromByPath(String path) {
PathExpression pathExpression = expressionFactory.createPathExpression(path);
joinManager.implicitJoin(pathExpression, true, null, null, false, false, true, false);
return (JoinNode) pathExpression.getBaseNode();
}
public boolean isEmpty() {
return joinManager.getRoots().isEmpty()
|| (
!fromClassExplicitelySet
&& joinManager.getRoots().size() == 1
&& joinManager.getRoots().get(0).getNodes().isEmpty()
&& joinManager.getRoots().get(0).getTreatedJoinNodes().isEmpty()
&& joinManager.getRoots().get(0).getEntityJoinNodes().isEmpty()
)
;
}
@SuppressWarnings("unchecked")
public BuilderType setFirstResult(int firstResult) {
this.firstResult = firstResult;
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setMaxResults(int maxResults) {
this.maxResults = maxResults;
return (BuilderType) this;
}
public int getFirstResult() {
return firstResult;
}
public int getMaxResults() {
return maxResults;
}
public EntityManager getEntityManager() {
return em;
}
public Metamodel getMetamodel() {
return mainQuery.metamodel;
}
@SuppressWarnings("unchecked")
public BuilderType setParameter(String name, Object value) {
parameterManager.satisfyParameter(name, value);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setParameter(String name, Calendar value, TemporalType temporalType) {
parameterManager.satisfyParameter(name, value, temporalType);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setParameter(String name, Date value, TemporalType temporalType) {
parameterManager.satisfyParameter(name, value, temporalType);
return (BuilderType) this;
}
public boolean containsParameter(String name) {
return parameterManager.containsParameter(name);
}
public boolean isParameterSet(String name) {
return parameterManager.isParameterSet(name);
}
public Parameter<?> getParameter(String name) {
return parameterManager.getParameter(name);
}
public Set<? extends Parameter<?>> getParameters() {
return parameterManager.getParameters();
}
public Object getParameterValue(String name) {
return parameterManager.getParameterValue(name);
}
@SuppressWarnings("unchecked")
public BuilderType setParameterType(String name, Class<?> type) {
parameterManager.setParameterType(name, type);
return (BuilderType) this;
}
/*
* Select methods
*/
@SuppressWarnings("unchecked")
public BuilderType distinct() {
prepareForModification();
selectManager.distinct();
return (BuilderType) this;
}
public CaseWhenStarterBuilder<BuilderType> selectCase() {
return selectCase(null);
}
/* CASE (WHEN condition THEN scalarExpression)+ ELSE scalarExpression END */
@SuppressWarnings("unchecked")
public CaseWhenStarterBuilder<BuilderType> selectCase(String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
prepareForModification();
return selectManager.selectCase((BuilderType) this, selectAlias);
}
public SimpleCaseWhenStarterBuilder<BuilderType> selectSimpleCase(String expression) {
return selectSimpleCase(expression, null);
}
/* CASE caseOperand (WHEN scalarExpression THEN scalarExpression)+ ELSE scalarExpression END */
@SuppressWarnings("unchecked")
public SimpleCaseWhenStarterBuilder<BuilderType> selectSimpleCase(String caseOperandExpression, String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
prepareForModification();
return selectManager.selectSimpleCase((BuilderType) this, selectAlias, expressionFactory.createCaseOperandExpression(caseOperandExpression));
}
public BuilderType select(String expression) {
return select(expression, null);
}
@SuppressWarnings("unchecked")
public BuilderType select(String expression, String selectAlias) {
Expression expr = expressionFactory.createSimpleExpression(expression, false);
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
verifyBuilderEnded();
prepareForModification();
selectManager.select(expr, selectAlias);
if (selectManager.getSelectInfos().size() > 1) {
// TODO: don't know if we should override this here
resultType = (Class<QueryResultType>) Tuple.class;
}
return (BuilderType) this;
}
public SubqueryInitiator<BuilderType> selectSubquery() {
return selectSubquery((String) null);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator<BuilderType> selectSubquery(String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
verifyBuilderEnded();
prepareForModification();
return selectManager.selectSubquery((BuilderType) this, selectAlias);
}
public SubqueryInitiator<BuilderType> selectSubquery(String subqueryAlias, String expression) {
return selectSubquery(subqueryAlias, expression, (String) null);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator<BuilderType> selectSubquery(String subqueryAlias, String expression, String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
if (subqueryAlias == null) {
throw new NullPointerException("subqueryAlias");
}
if (subqueryAlias.isEmpty()) {
throw new IllegalArgumentException("subqueryAlias");
}
if (expression == null) {
throw new NullPointerException("expression");
}
if (!expression.contains(subqueryAlias)) {
throw new IllegalArgumentException("Expression [" + expression + "] does not contain subquery alias [" + subqueryAlias + "]");
}
verifyBuilderEnded();
prepareForModification();
return selectManager.selectSubquery((BuilderType) this, subqueryAlias, expressionFactory.createSimpleExpression(expression, false), selectAlias);
}
public MultipleSubqueryInitiator<BuilderType> selectSubqueries(String expression) {
return selectSubqueries(null, expression);
}
@SuppressWarnings("unchecked")
public MultipleSubqueryInitiator<BuilderType> selectSubqueries(String selectAlias, String expression) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
if (expression == null) {
throw new NullPointerException("expression");
}
verifyBuilderEnded();
prepareForModification();
return selectManager.selectSubqueries((BuilderType) this, expressionFactory.createSimpleExpression(expression, false), selectAlias);
}
public SubqueryBuilder<BuilderType> selectSubquery(FullQueryBuilder<?, ?> criteriaBuilder) {
return selectSubquery(null, criteriaBuilder);
}
@SuppressWarnings("unchecked")
public SubqueryBuilder<BuilderType> selectSubquery(String selectAlias, FullQueryBuilder<?, ?> criteriaBuilder) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
if (criteriaBuilder == null) {
throw new NullPointerException("criteriaBuilder");
}
verifyBuilderEnded();
prepareForModification();
return selectManager.selectSubquery((BuilderType) this, selectAlias, criteriaBuilder);
}
public SubqueryBuilder<BuilderType> selectSubquery(String subqueryAlias, String expression, FullQueryBuilder<?, ?> criteriaBuilder) {
return selectSubquery(subqueryAlias, expression, null, criteriaBuilder);
}
@SuppressWarnings("unchecked")
public SubqueryBuilder<BuilderType> selectSubquery(String subqueryAlias, String expression, String selectAlias, FullQueryBuilder<?, ?> criteriaBuilder) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
if (subqueryAlias == null) {
throw new NullPointerException("subqueryAlias");
}
if (subqueryAlias.isEmpty()) {
throw new IllegalArgumentException("subqueryAlias");
}
if (expression == null) {
throw new NullPointerException("expression");
}
if (criteriaBuilder == null) {
throw new NullPointerException("criteriaBuilder");
}
if (!expression.contains(subqueryAlias)) {
throw new IllegalArgumentException("Expression [" + expression + "] does not contain subquery alias [" + subqueryAlias + "]");
}
verifyBuilderEnded();
prepareForModification();
return selectManager.selectSubquery((BuilderType) this, subqueryAlias, expressionFactory.createSimpleExpression(expression, false), selectAlias, criteriaBuilder);
}
/*
* Where methods
*/
public RestrictionBuilder<BuilderType> where(String expression) {
prepareForModification();
Expression expr = expressionFactory.createSimpleExpression(expression, false);
return whereManager.restrict(this, expr);
}
public CaseWhenStarterBuilder<RestrictionBuilder<BuilderType>> whereCase() {
prepareForModification();
return whereManager.restrictCase(this);
}
public SimpleCaseWhenStarterBuilder<RestrictionBuilder<BuilderType>> whereSimpleCase(String expression) {
prepareForModification();
return whereManager.restrictSimpleCase(this, expressionFactory.createCaseOperandExpression(expression));
}
public WhereOrBuilder<BuilderType> whereOr() {
prepareForModification();
return whereManager.whereOr(this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator<BuilderType> whereExists() {
prepareForModification();
return whereManager.restrictExists((BuilderType) this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator<BuilderType> whereNotExists() {
prepareForModification();
return whereManager.restrictNotExists((BuilderType) this);
}
@SuppressWarnings("unchecked")
public SubqueryBuilder<BuilderType> whereExists(FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
return whereManager.restrictExists((BuilderType) this, criteriaBuilder);
}
@SuppressWarnings("unchecked")
public SubqueryBuilder<BuilderType> whereNotExists(FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
return whereManager.restrictNotExists((BuilderType) this, criteriaBuilder);
}
public SubqueryInitiator<RestrictionBuilder<BuilderType>> whereSubquery() {
prepareForModification();
return whereManager.restrict(this);
}
public SubqueryInitiator<RestrictionBuilder<BuilderType>> whereSubquery(String subqueryAlias, String expression) {
prepareForModification();
return whereManager.restrict(this, subqueryAlias, expression);
}
public MultipleSubqueryInitiator<RestrictionBuilder<BuilderType>> whereSubqueries(String expression) {
prepareForModification();
return whereManager.restrictSubqueries(this, expression);
}
public SubqueryBuilder<RestrictionBuilder<BuilderType>> whereSubquery(FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
return whereManager.restrict(this, criteriaBuilder);
}
public SubqueryBuilder<RestrictionBuilder<BuilderType>> whereSubquery(String subqueryAlias, String expression, FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
return whereManager.restrict(this, subqueryAlias, expression, criteriaBuilder);
}
@SuppressWarnings("unchecked")
public BuilderType whereExpression(String expression) {
prepareForModification();
Predicate predicate = expressionFactory.createBooleanExpression(expression, false);
whereManager.restrictExpression(this, predicate);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public MultipleSubqueryInitiator<BuilderType> whereExpressionSubqueries(String expression) {
prepareForModification();
Predicate predicate = expressionFactory.createBooleanExpression(expression, true);
return whereManager.restrictExpressionSubqueries((BuilderType) this, predicate);
}
/*
* Group by methods
*/
@SuppressWarnings("unchecked")
public BuilderType groupBy(String... paths) {
for (String path : paths) {
groupBy(path);
}
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType groupBy(String expression) {
prepareForModification();
Expression expr;
if (mainQuery.getQueryConfiguration().isCompatibleModeEnabled()) {
expr = expressionFactory.createPathExpression(expression);
} else {
expr = expressionFactory.createSimpleExpression(expression, false);
Set<Expression> collectedExpressions = groupByExpressionGatheringVisitor.extractGroupByExpressions(expr);
if (collectedExpressions.size() > 1 || collectedExpressions.iterator().next() != expr) {
throw new RuntimeException("The complex group by expression [" + expression + "] is not supported by the underlying database. The valid sub-expressions are: " + collectedExpressions);
}
}
verifyBuilderEnded();
groupByManager.groupBy(expr);
return (BuilderType) this;
}
/*
* Having methods
*/
public RestrictionBuilder<BuilderType> having(String expression) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
Expression expr = expressionFactory.createSimpleExpression(expression, false);
return havingManager.restrict(this, expr);
}
public CaseWhenStarterBuilder<RestrictionBuilder<BuilderType>> havingCase() {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictCase(this);
}
public SimpleCaseWhenStarterBuilder<RestrictionBuilder<BuilderType>> havingSimpleCase(String expression) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictSimpleCase(this, expressionFactory.createCaseOperandExpression(expression));
}
public HavingOrBuilder<BuilderType> havingOr() {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.havingOr(this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator<BuilderType> havingExists() {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictExists((BuilderType) this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator<BuilderType> havingNotExists() {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictNotExists((BuilderType) this);
}
@SuppressWarnings("unchecked")
public SubqueryBuilder<BuilderType> havingExists(FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictExists((BuilderType) this, criteriaBuilder);
}
@SuppressWarnings("unchecked")
public SubqueryBuilder<BuilderType> havingNotExists(FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictNotExists((BuilderType) this, criteriaBuilder);
}
public SubqueryInitiator<RestrictionBuilder<BuilderType>> havingSubquery() {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrict(this);
}
public SubqueryInitiator<RestrictionBuilder<BuilderType>> havingSubquery(String subqueryAlias, String expression) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrict(this, subqueryAlias, expression);
}
public MultipleSubqueryInitiator<RestrictionBuilder<BuilderType>> havingSubqueries(String expression) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictSubqueries(this, expression);
}
public SubqueryBuilder<RestrictionBuilder<BuilderType>> havingSubquery(FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrict(this, criteriaBuilder);
}
public SubqueryBuilder<RestrictionBuilder<BuilderType>> havingSubquery(String subqueryAlias, String expression, FullQueryBuilder<?, ?> criteriaBuilder) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrict(this, subqueryAlias, expression, criteriaBuilder);
}
@SuppressWarnings("unchecked")
public BuilderType havingExpression(String expression) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
Predicate predicate = expressionFactory.createBooleanExpression(expression, false);
havingManager.restrictExpression(this, predicate);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public MultipleSubqueryInitiator<BuilderType> havingExpressionSubqueries(String expression) {
prepareForModification();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
Predicate predicate = expressionFactory.createBooleanExpression(expression, true);
return havingManager.restrictExpressionSubqueries((BuilderType) this, predicate);
}
/*
* Order by methods
*/
public BuilderType orderByDesc(String expression) {
return orderBy(expression, false, false);
}
public BuilderType orderByAsc(String expression) {
return orderBy(expression, true, false);
}
public BuilderType orderByDesc(String expression, boolean nullFirst) {
return orderBy(expression, false, nullFirst);
}
public BuilderType orderByAsc(String expression, boolean nullFirst) {
return orderBy(expression, true, nullFirst);
}
@SuppressWarnings("unchecked")
public BuilderType orderBy(String expression, boolean ascending, boolean nullFirst) {
Expression expr;
if (mainQuery.getQueryConfiguration().isCompatibleModeEnabled()) {
expr = expressionFactory.createOrderByExpression(expression);
} else {
expr = expressionFactory.createSimpleExpression(expression, false);
}
orderBy(expr, ascending, nullFirst);
return (BuilderType) this;
}
private void orderBy(Expression expression, boolean ascending, boolean nullFirst) {
prepareForModification();
verifyBuilderEnded();
orderByManager.orderBy(expression, ascending, nullFirst);
}
protected void verifySetBuilderEnded() {
if (finalSetOperationBuilder != null) {
if (!setOperationEnded) {
throw new IllegalStateException("Set operation builder not properly ended!");
}
}
}
protected void verifyBuilderEnded() {
if (isMainQuery) {
mainQuery.cteManager.verifyBuilderEnded();
}
whereManager.verifyBuilderEnded();
keysetManager.verifyBuilderEnded();
havingManager.verifyBuilderEnded();
selectManager.verifyBuilderEnded();
joinManager.verifyBuilderEnded();
}
/*
* Join methods
*/
public BuilderType innerJoin(String path, String alias) {
return join(path, alias, JoinType.INNER);
}
public BuilderType innerJoinDefault(String path, String alias) {
return joinDefault(path, alias, JoinType.INNER);
}
public BuilderType leftJoin(String path, String alias) {
return join(path, alias, JoinType.LEFT);
}
public BuilderType leftJoinDefault(String path, String alias) {
return joinDefault(path, alias, JoinType.LEFT);
}
public BuilderType rightJoin(String path, String alias) {
return join(path, alias, JoinType.RIGHT);
}
public BuilderType rightJoinDefault(String path, String alias) {
return joinDefault(path, alias, JoinType.RIGHT);
}
@SuppressWarnings("unchecked")
public BuilderType join(String path, String alias, JoinType type) {
prepareForModification();
checkJoinPreconditions(path, alias, type);
joinManager.join(path, alias, type, false, false);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType joinDefault(String path, String alias, JoinType type) {
prepareForModification();
checkJoinPreconditions(path, alias, type);
joinManager.join(path, alias, type, false, true);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public JoinOnBuilder<BuilderType> joinOn(String path, String alias, JoinType type) {
prepareForModification();
checkJoinPreconditions(path, alias, type);
return joinManager.joinOn((BuilderType) this, path, alias, type, false);
}
@SuppressWarnings("unchecked")
public JoinOnBuilder<BuilderType> joinDefaultOn(String path, String alias, JoinType type) {
prepareForModification();
checkJoinPreconditions(path, alias, type);
return joinManager.joinOn((BuilderType) this, path, alias, type, true);
}
@SuppressWarnings("unchecked")
public JoinOnBuilder<BuilderType> joinOn(Class<?> clazz, String alias, JoinType type) {
return joinOn(joinManager.getRootNodeOrFail("An explicit base join node is required when multiple root nodes are used!").getAlias(), clazz, alias, type);
}
@SuppressWarnings("unchecked")
public JoinOnBuilder<BuilderType> joinOn(String base, Class<?> entityClass, String alias, JoinType type) {
prepareForModification();
checkJoinPreconditions(base, alias, type);
if (entityClass == null) {
throw new NullPointerException("entityClass");
}
return joinManager.joinOn((BuilderType) this, base, entityClass, alias, type);
}
public JoinOnBuilder<BuilderType> innerJoinOn(String path, String alias) {
return joinOn(path, alias, JoinType.INNER);
}
public JoinOnBuilder<BuilderType> innerJoinDefaultOn(String path, String alias) {
return joinDefaultOn(path, alias, JoinType.INNER);
}
public JoinOnBuilder<BuilderType> innerJoinOn(Class<?> clazz, String alias) {
return joinOn(clazz, alias, JoinType.INNER);
}
public JoinOnBuilder<BuilderType> innerJoinOn(String base, Class<?> clazz, String alias) {
return joinOn(base, clazz, alias, JoinType.INNER);
}
public JoinOnBuilder<BuilderType> leftJoinOn(String path, String alias) {
return joinOn(path, alias, JoinType.LEFT);
}
public JoinOnBuilder<BuilderType> leftJoinDefaultOn(String path, String alias) {
return joinDefaultOn(path, alias, JoinType.LEFT);
}
public JoinOnBuilder<BuilderType> leftJoinOn(Class<?> clazz, String alias) {
return joinOn(clazz, alias, JoinType.LEFT);
}
public JoinOnBuilder<BuilderType> leftJoinOn(String base, Class<?> clazz, String alias) {
return joinOn(base, clazz, alias, JoinType.LEFT);
}
public JoinOnBuilder<BuilderType> rightJoinOn(String path, String alias) {
return joinOn(path, alias, JoinType.RIGHT);
}
public JoinOnBuilder<BuilderType> rightJoinDefaultOn(String path, String alias) {
return joinDefaultOn(path, alias, JoinType.RIGHT);
}
public JoinOnBuilder<BuilderType> rightJoinOn(Class<?> clazz, String alias) {
return joinOn(clazz, alias, JoinType.RIGHT);
}
public JoinOnBuilder<BuilderType> rightJoinOn(String base, Class<?> clazz, String alias) {
return joinOn(base, clazz, alias, JoinType.RIGHT);
}
private void checkJoinPreconditions(String path, String alias, JoinType type) {
if (path == null) {
throw new NullPointerException("path");
}
if (alias == null) {
throw new NullPointerException("alias");
}
if (type == null) {
throw new NullPointerException("type");
}
if (alias.isEmpty()) {
throw new IllegalArgumentException("Empty alias");
}
verifyBuilderEnded();
}
protected boolean isJoinRequiredForSelect() {
return true;
}
protected void applyImplicitJoins() {
if (implicitJoinsApplied) {
return;
}
final JoinVisitor joinVisitor = new JoinVisitor(mainQuery.parameterTransformerFactory, joinManager, parameterManager, !jpaProvider.supportsSingleValuedAssociationIdExpressions());
final List<JoinNode> fetchableNodes = new ArrayList<>();
final JoinNodeVisitor joinNodeVisitor = new OnClauseJoinNodeVisitor(joinVisitor) {
@Override
public void visit(JoinNode node) {
super.visit(node);
node.registerDependencies();
if (node.isFetch()) {
fetchableNodes.add(node);
}
}
};
joinVisitor.setFromClause(ClauseType.JOIN);
joinManager.acceptVisitor(joinNodeVisitor);
// carry out implicit joins
joinVisitor.setFromClause(ClauseType.SELECT);
// There might be clauses for which joins are not required
joinVisitor.setJoinRequired(isJoinRequiredForSelect());
selectManager.acceptVisitor(joinVisitor);
joinVisitor.setJoinRequired(true);
// Only the main query has fetch owners
if (isMainQuery) {
StringBuilder sb = null;
Set<JoinNode> fetchOwners = selectManager.collectFetchOwners();
nodesToFetch = new HashSet<>();
// Add all parents of the fetchable nodes to nodesToFetch until the fetch owner is reached
// If we reach a root before a fetch owner, the fetch owner is missing
for (int i = 0; i < fetchableNodes.size(); i++) {
JoinNode fetchableNode = fetchableNodes.get(i);
while (!fetchOwners.contains(fetchableNode)) {
nodesToFetch.add(fetchableNode);
if (fetchableNode.getParent() == null) {
if (sb == null) {
sb = new StringBuilder();
sb.append("Some join nodes specified fetch joining but their fetch owners weren't included in the select clause! Missing fetch owners: [");
} else {
sb.append(", ");
}
sb.append(fetchableNode.getAlias());
break;
}
fetchableNode = fetchableNode.getParent();
// We don't care about treated nodes specifically when fetching as they aren't declarable directly, but only the "main" node
if (fetchableNode.isTreatedJoinNode()) {
fetchableNode = ((TreatedJoinAliasInfo) fetchableNode.getAliasInfo()).getTreatedJoinNode();
}
}
}
if (sb != null) {
sb.append("]");
throw new IllegalStateException(sb.toString());
}
} else {
nodesToFetch = Collections.emptySet();
}
joinVisitor.setFromClause(ClauseType.WHERE);
whereManager.acceptVisitor(joinVisitor);
joinVisitor.setFromClause(ClauseType.GROUP_BY);
groupByManager.acceptVisitor(joinVisitor);
joinVisitor.setFromClause(ClauseType.HAVING);
havingManager.acceptVisitor(joinVisitor);
joinVisitor.setJoinWithObjectLeafAllowed(false);
joinVisitor.setFromClause(ClauseType.ORDER_BY);
orderByManager.acceptVisitor(joinVisitor);
joinVisitor.setJoinWithObjectLeafAllowed(true);
// No need to implicit join again if no mutation occurs
implicitJoinsApplied = true;
}
protected void applyVisitor(VisitorAdapter expressionVisitor) {
selectManager.acceptVisitor(expressionVisitor);
joinManager.acceptVisitor(new OnClauseJoinNodeVisitor(expressionVisitor));
whereManager.acceptVisitor(expressionVisitor);
groupByManager.acceptVisitor(expressionVisitor);
havingManager.acceptVisitor(expressionVisitor);
orderByManager.acceptVisitor(expressionVisitor);
}
public void applyExpressionTransformers() {
int size = transformerGroups.size();
for (int i = 0; i < size; i++) {
ExpressionTransformerGroup transformerGroup = transformerGroups.get(i);
transformerGroup.applyExpressionTransformer(joinManager);
transformerGroup.applyExpressionTransformer(selectManager);
transformerGroup.applyExpressionTransformer(whereManager);
transformerGroup.applyExpressionTransformer(groupByManager);
transformerGroup.applyExpressionTransformer(havingManager);
transformerGroup.applyExpressionTransformer(orderByManager);
transformerGroup.afterGlobalTransformation();
}
// After all transformations are done, we can finally check if aggregations are used
hasGroupBy = groupByManager.hasGroupBys();
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(selectManager.acceptVisitor(AggregateDetectionVisitor.INSTANCE, true));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(joinManager.acceptVisitor(AggregateDetectionVisitor.INSTANCE, true));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(whereManager.acceptVisitor(AggregateDetectionVisitor.INSTANCE));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(orderByManager.acceptVisitor(AggregateDetectionVisitor.INSTANCE, true));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(havingManager.acceptVisitor(AggregateDetectionVisitor.INSTANCE));
}
public Class<QueryResultType> getResultType() {
return resultType;
}
public String getQueryString() {
prepareAndCheck();
return getExternalQueryString();
}
protected String getBaseQueryStringWithCheck() {
prepareAndCheck();
return getBaseQueryString();
}
protected final TypedQuery<QueryResultType> getTypedQueryForFinalOperationBuilder() {
try {
checkSetBuilderEnded = false;
return getTypedQuery();
} finally {
checkSetBuilderEnded = true;
}
}
protected TypedQuery<QueryResultType> getTypedQuery() {
// NOTE: This must happen first because it generates implicit joins
String baseQueryString = getBaseQueryStringWithCheck();
// We can only use the query directly if we have no ctes, entity functions or hibernate bugs
Set<JoinNode> keyRestrictedLeftJoins = joinManager.getKeyRestrictedLeftJoins();
final boolean needsSqlReplacement = isMainQuery && mainQuery.cteManager.hasCtes() || joinManager.hasEntityFunctions() || !keyRestrictedLeftJoins.isEmpty() || !isMainQuery && hasLimit();
if (!needsSqlReplacement) {
TypedQuery<QueryResultType> baseQuery = getTypedQuery(baseQueryString);
parameterManager.parameterizeQuery(baseQuery);
return baseQuery;
}
TypedQuery<QueryResultType> baseQuery = getTypedQuery(baseQueryString);
Set<String> parameterListNames = parameterManager.getParameterListNames(baseQuery);
String limit = null;
String offset = null;
// The main query will handle that separately
if (!isMainQuery) {
if (firstResult != 0) {
offset = Integer.toString(firstResult);
}
if (maxResults != Integer.MAX_VALUE) {
limit = Integer.toString(maxResults);
}
}
List<String> keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, Collections.EMPTY_SET);
List<EntityFunctionNode> entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List<CTENode> ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterListNames, limit, offset, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
TypedQuery<QueryResultType> query = new CustomSQLTypedQuery<QueryResultType>(
querySpecification,
baseQuery,
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(query);
return applyObjectBuilder(query);
}
protected List<String> getKeyRestrictedLeftJoinAliases(Query baseQuery, Set<JoinNode> keyRestrictedLeftJoins, Set<ClauseType> clauseExclusions) {
List<String> keyRestrictedLeftJoinAliases = new ArrayList<String>();
if (!keyRestrictedLeftJoins.isEmpty()) {
for (JoinNode node : keyRestrictedLeftJoins) {
if (!clauseExclusions.isEmpty() && clauseExclusions.containsAll(node.getClauseDependencies()) && !node.isCardinalityMandatory()) {
continue;
}
// The alias of the target entity table
String sqlAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, node.getAliasInfo().getAlias());
keyRestrictedLeftJoinAliases.add(sqlAlias);
}
}
return keyRestrictedLeftJoinAliases;
}
protected List<EntityFunctionNode> getEntityFunctionNodes(Query baseQuery) {
List<EntityFunctionNode> entityFunctionNodes = new ArrayList<EntityFunctionNode>();
for (JoinNode node : joinManager.getEntityFunctionNodes()) {
String valuesClause = node.getValuesClause();
String valuesAliases = node.getValuesAliases();
String valuesTableSqlAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, node.getAlias());
entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, node.getType(), valuesTableSqlAlias, node.getValueQuery()));
}
return entityFunctionNodes;
}
protected boolean renderCteNodes(boolean isSubquery) {
return isMainQuery && !isSubquery;
}
protected List<CTENode> getCteNodes(Query baseQuery, boolean isSubquery) {
List<CTENode> cteNodes = new ArrayList<CTENode>();
// NOTE: Delete statements could cause CTEs to be generated for the cascading deletes
if (!isMainQuery || isSubquery || !dbmsDialect.supportsWithClause() || !mainQuery.cteManager.hasCtes() && statementType != DbmsStatementType.DELETE || statementType != DbmsStatementType.SELECT && !mainQuery.dbmsDialect.supportsWithClauseInModificationQuery()) {
return cteNodes;
}
StringBuilder sb = new StringBuilder();
for (CTEInfo cteInfo : mainQuery.cteManager.getCtes()) {
// Build queries and add as participating queries
Map<DbmsModificationState, String> modificationStates = cteInfo.nonRecursiveCriteriaBuilder.getModificationStates(explicitVersionEntities);
Query nonRecursiveQuery = cteInfo.nonRecursiveCriteriaBuilder.getQuery(modificationStates);
QuerySpecification<?> nonRecursiveQuerySpecification = getQuerySpecification(nonRecursiveQuery);
Map<String, String> nonRecursiveTableNameRemappings = null;
if (nonRecursiveQuery instanceof CustomSQLQuery) {
// EntityAlias -> CteName
nonRecursiveTableNameRemappings = cteInfo.nonRecursiveCriteriaBuilder.getModificationStateRelatedTableNameRemappings(explicitVersionEntities);
}
Query recursiveQuery;
QuerySpecification<?> recursiveQuerySpecification = null;
Map<String, String> recursiveTableNameRemappings = null;
if (cteInfo.recursive) {
modificationStates = cteInfo.nonRecursiveCriteriaBuilder.getModificationStates(explicitVersionEntities);
recursiveQuery = cteInfo.recursiveCriteriaBuilder.getQuery(modificationStates);
if (!dbmsDialect.supportsJoinsInRecursiveCte() && cteInfo.recursiveCriteriaBuilder.joinManager.hasJoins()) {
throw new IllegalStateException("The dbms dialect does not support joins in the recursive part of a CTE!");
}
recursiveQuerySpecification = getQuerySpecification(recursiveQuery);
if (recursiveQuery instanceof CustomSQLQuery) {
// EntityAlias -> CteName
recursiveTableNameRemappings = cteInfo.recursiveCriteriaBuilder.getModificationStateRelatedTableNameRemappings(explicitVersionEntities);
}
}
String cteName = cteInfo.cteType.getName();
final List<String> columnNames = cteInfo.columnNames;
String head;
String[] aliases;
if (dbmsDialect.supportsWithClauseHead()) {
sb.setLength(0);
sb.append(cteName);
sb.append('(');
for (int i = 0; i < columnNames.size(); i++) {
String column = columnNames.get(i);
if (i != 0) {
sb.append(", ");
}
sb.append(column);
}
sb.append(')');
head = sb.toString();
aliases = null;
} else {
sb.setLength(0);
sb.append(cteName);
List<String> list = new ArrayList<>(columnNames.size());
for (int i = 0; i < columnNames.size(); i++) {
String[] columns = cbf.getExtendedQuerySupport().getColumnNames(em, cteInfo.cteType, columnNames.get(i));
for (String column : columns) {
list.add(column);
}
}
head = sb.toString();
aliases = list.toArray(new String[list.size()]);
}
String nonRecursiveWithClauseSuffix = null;
if (!cteInfo.recursive && !dbmsDialect.supportsNonRecursiveWithClause()) {
sb.setLength(0);
sb.append("\nUNION ALL\n");
sb.append("SELECT ");
sb.append("NULL");
for (int i = 1; i < columnNames.size(); i++) {
sb.append(", ");
sb.append("NULL");
}
sb.append(" FROM DUAL WHERE 1=0");
nonRecursiveWithClauseSuffix = sb.toString();
}
cteNodes.add(new CTENode(
cteInfo.name,
cteInfo.cteType.getName(),
head,
aliases,
cteInfo.unionAll,
nonRecursiveQuerySpecification,
recursiveQuerySpecification,
nonRecursiveTableNameRemappings,
recursiveTableNameRemappings,
nonRecursiveWithClauseSuffix
));
}
return cteNodes;
}
protected Query getQuery() {
return getTypedQuery();
}
protected Query getQuery(Map<DbmsModificationState, String> includedModificationStates) {
return getQuery();
}
@SuppressWarnings("unchecked")
protected TypedQuery<QueryResultType> getTypedQuery(String queryString) {
TypedQuery<QueryResultType> query = (TypedQuery<QueryResultType>) em.createQuery(queryString, selectManager.getExpectedQueryResultType());
if (firstResult != 0) {
query.setFirstResult(firstResult);
}
if (maxResults != Integer.MAX_VALUE) {
query.setMaxResults(maxResults);
}
return applyObjectBuilder(query);
}
@SuppressWarnings("unchecked")
public KeysetBuilder<BuilderType> beforeKeyset() {
prepareForModification();
return keysetManager.startBuilder(new KeysetBuilderImpl<BuilderType>((BuilderType) this, keysetManager, KeysetMode.PREVIOUS));
}
public BuilderType beforeKeyset(Serializable... values) {
return beforeKeyset(new KeysetImpl(values));
}
@SuppressWarnings("unchecked")
public BuilderType beforeKeyset(Keyset keyset) {
prepareForModification();
keysetManager.verifyBuilderEnded();
keysetManager.setKeysetLink(new SimpleKeysetLink(keyset, KeysetMode.PREVIOUS));
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public KeysetBuilder<BuilderType> afterKeyset() {
prepareForModification();
return keysetManager.startBuilder(new KeysetBuilderImpl<BuilderType>((BuilderType) this, keysetManager, KeysetMode.NEXT));
}
public BuilderType afterKeyset(Serializable... values) {
return afterKeyset(new KeysetImpl(values));
}
@SuppressWarnings("unchecked")
public BuilderType afterKeyset(Keyset keyset) {
prepareForModification();
keysetManager.verifyBuilderEnded();
keysetManager.setKeysetLink(new SimpleKeysetLink(keyset, KeysetMode.NEXT));
return (BuilderType) this;
}
protected String getBaseQueryString() {
if (cachedQueryString == null) {
cachedQueryString = buildBaseQueryString(false);
}
return cachedQueryString;
}
protected String getExternalQueryString() {
if (cachedExternalQueryString == null) {
cachedExternalQueryString = buildExternalQueryString();
}
return cachedExternalQueryString;
}
protected void prepareForModification() {
if (setOperationEnded) {
throw new IllegalStateException("Modifications to a query after connecting with a set operation is not allowed!");
}
needsCheck = true;
cachedQueryString = null;
cachedExternalQueryString = null;
implicitJoinsApplied = false;
}
protected void prepareAndCheck() {
if (checkSetBuilderEnded) {
verifySetBuilderEnded();
}
if (!needsCheck) {
return;
}
verifyBuilderEnded();
joinManager.acceptVisitor(new JoinNodeVisitor() {
@Override
public void visit(JoinNode node) {
Class<?> cteType = node.getType();
// Except for VALUES clause from nodes, every cte type must be defined
if (node.getValueQuery() == null && mainQuery.metamodel.getCte(cteType) != null) {
if (mainQuery.cteManager.getCte(cteType) == null) {
throw new IllegalStateException("Usage of CTE '" + cteType.getName() + "' without definition!");
}
}
}
});
// resolve unresolved aliases, object model etc.
// we must do implicit joining at the end because we can only do
// the aliases resolving at the end and alias resolving must happen before
// the implicit joins
// it makes no sense to do implicit joining before this point, since
// the user can call the api in arbitrary orders
// so where("b.c").join("a.b") but also
// join("a.b", "b").where("b.c")
// in the first case
applyImplicitJoins();
applyExpressionTransformers();
if (keysetManager.hasKeyset()) {
// The last order by expression must be unique, otherwise keyset scrolling wouldn't work
List<OrderByExpression> orderByExpressions = orderByManager.getOrderByExpressions(cbf.getMetamodel());
if (!orderByExpressions.get(orderByExpressions.size() - 1).isUnique()) {
throw new IllegalStateException("The last order by item must be unique!");
}
keysetManager.initialize(orderByExpressions);
}
// No need to do all that stuff again if no mutation occurs
needsCheck = false;
}
protected String buildBaseQueryString(boolean externalRepresentation) {
StringBuilder sbSelectFrom = new StringBuilder();
buildBaseQueryString(sbSelectFrom, externalRepresentation);
return sbSelectFrom.toString();
}
protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) {
appendSelectClause(sbSelectFrom);
List<String> whereClauseConjuncts = appendFromClause(sbSelectFrom, externalRepresentation);
appendWhereClause(sbSelectFrom, whereClauseConjuncts);
appendGroupByClause(sbSelectFrom);
appendOrderByClause(sbSelectFrom);
if (externalRepresentation && !isMainQuery) {
// Don't render the LIMIT clause for subqueries, but let the parent render it in a LIMIT function
if (!(this instanceof SubqueryInternalBuilder<?>)) {
applyJpaLimit(sbSelectFrom);
}
}
}
protected String buildExternalQueryString() {
StringBuilder sbSelectFrom = new StringBuilder();
buildExternalQueryString(sbSelectFrom);
return sbSelectFrom.toString();
}
protected void buildExternalQueryString(StringBuilder sbSelectFrom) {
if (isMainQuery) {
mainQuery.cteManager.buildClause(sbSelectFrom);
}
buildBaseQueryString(sbSelectFrom, true);
}
protected void appendSelectClause(StringBuilder sbSelectFrom) {
selectManager.buildSelect(sbSelectFrom, false);
}
protected List<String> appendFromClause(StringBuilder sbSelectFrom, boolean externalRepresentation) {
List<String> whereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, nodesToFetch);
return whereClauseConjuncts;
}
protected void appendWhereClause(StringBuilder sbSelectFrom) {
appendWhereClause(sbSelectFrom, Collections.<String>emptyList());
}
protected void appendWhereClause(StringBuilder sbSelectFrom, List<String> whereClauseConjuncts) {
KeysetLink keysetLink = keysetManager.getKeysetLink();
if (keysetLink == null || keysetLink.getKeysetMode() == KeysetMode.NONE) {
whereManager.buildClause(sbSelectFrom, whereClauseConjuncts);
} else {
sbSelectFrom.append(" WHERE ");
keysetManager.buildKeysetPredicate(sbSelectFrom);
if (whereManager.hasPredicates()) {
sbSelectFrom.append(" AND ");
whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts);
}
}
}
protected void appendGroupByClause(StringBuilder sbSelectFrom) {
Set<String> clauses = new LinkedHashSet<String>();
groupByManager.buildGroupByClauses(clauses);
int size = transformerGroups.size();
for (int i = 0; i < size; i++) {
ExpressionTransformerGroup<?> transformerGroup = transformerGroups.get(i);
clauses.addAll(transformerGroup.getRequiredGroupByClauses());
}
if (hasGroupBy) {
if (mainQuery.getQueryConfiguration().isImplicitGroupByFromSelectEnabled()) {
selectManager.buildGroupByClauses(cbf.getMetamodel(), clauses);
}
if (mainQuery.getQueryConfiguration().isImplicitGroupByFromHavingEnabled()) {
havingManager.buildGroupByClauses(clauses);
}
if (mainQuery.getQueryConfiguration().isImplicitGroupByFromOrderByEnabled()) {
orderByManager.buildGroupByClauses(clauses, false);
}
}
if (!clauses.isEmpty()) {
for (int i = 0; i < size; i++) {
ExpressionTransformerGroup<?> transformerGroup = transformerGroups.get(i);
clauses.addAll(transformerGroup.getOptionalGroupByClauses());
}
}
groupByManager.buildGroupBy(sbSelectFrom, clauses);
havingManager.buildClause(sbSelectFrom);
}
protected void appendOrderByClause(StringBuilder sbSelectFrom) {
queryGenerator.setResolveSelectAliases(false);
orderByManager.buildOrderBy(sbSelectFrom, false, false);
queryGenerator.setResolveSelectAliases(true);
}
protected Map<DbmsModificationState, String> getModificationStates(Map<Class<?>, Map<String, DbmsModificationState>> explicitVersionEntities) {
return null;
}
protected Map<String, String> getModificationStateRelatedTableNameRemappings(Map<Class<?>, Map<String, DbmsModificationState>> explicitVersionEntities) {
return null;
}
protected static boolean isEmpty(Set<JoinNode> joinNodes, Set<ClauseType> clauseExclusions) {
for (JoinNode node : joinNodes) {
if (!clauseExclusions.isEmpty() && clauseExclusions.containsAll(node.getClauseDependencies()) && !node.isCardinalityMandatory()) {
continue;
}
return false;
}
return true;
}
private QuerySpecification<?> getQuerySpecification(Query query) {
if (query instanceof AbstractCustomQuery<?>) {
return ((AbstractCustomQuery<?>) query).getQuerySpecification();
}
return new DefaultQuerySpecification(statementType, query, em, parameterManager.getParameterListNames(query), cbf.getExtendedQuerySupport());
}
protected boolean hasLimit() {
return firstResult != 0 || maxResults != Integer.MAX_VALUE;
}
protected void applyJpaLimit(StringBuilder sbSelectFrom) {
if (hasLimit()) {
sbSelectFrom.append(" LIMIT ");
sbSelectFrom.append(maxResults);
if (firstResult > 0) {
sbSelectFrom.append(" OFFSET ");
sbSelectFrom.append(firstResult);
}
}
}
@SuppressWarnings("unchecked")
protected final TypedQuery<QueryResultType> applyObjectBuilder(TypedQuery<?> query) {
if (selectManager.getSelectObjectBuilder() != null) {
return new ObjectBuilderTypedQuery<>(query, selectManager.getSelectObjectBuilder());
} else {
return (TypedQuery<QueryResultType>) query;
}
}
// TODO: needs equals-hashCode implementation
}