
com.blazebit.persistence.impl.AbstractCommonQueryBuilder Maven / Gradle / Ivy
/*
* Copyright 2014 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 java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
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.Metamodel;
import com.blazebit.persistence.CaseWhenStarterBuilder;
import com.blazebit.persistence.CommonQueryBuilder;
import com.blazebit.persistence.CriteriaBuilderFactory;
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.LeafOngoingSetOperationCTECriteriaBuilder;
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.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.VisitorAdapter;
import com.blazebit.persistence.impl.jpaprovider.JpaProvider;
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.util.PropertyUtils;
import com.blazebit.persistence.spi.DbmsDialect;
import com.blazebit.persistence.spi.DbmsModificationState;
import com.blazebit.persistence.spi.DbmsStatementType;
import com.blazebit.persistence.spi.QueryTransformer;
import com.blazebit.persistence.spi.SetOperationType;
/**
*
* @param The query result type
* @param The concrete builder type
* @param The builder type that should be returned on set operations
* @param The builder type that should be returned on subquery set operations
* @author Christian Beikov
* @author Moritz Becker
* @since 1.0
*/
public abstract class AbstractCommonQueryBuilder> {
protected static final Logger LOG = Logger.getLogger(CriteriaBuilderImpl.class.getName());
public static final String idParamName = "ids";
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, Map> explicitVersionEntities = new HashMap, Map>(0);
protected final ParameterManager parameterManager;
protected final SelectManager selectManager;
protected final WhereManager whereManager;
protected final HavingManager havingManager;
protected final GroupByManager groupByManager;
protected final OrderByManager orderByManager;
protected final JoinManager joinManager;
protected final KeysetManager keysetManager;
protected final CTEManager cteManager;
protected final ResolvingQueryGenerator queryGenerator;
protected final SubqueryInitiatorFactory subqueryInitFactory;
// This builder will be passed in when using set operations
protected final FinalSetReturn finalSetOperationBuilder;
protected final DbmsDialect dbmsDialect;
protected final JpaProvider jpaProvider;
protected final Set registeredFunctions;
protected final AliasManager aliasManager;
protected final ExpressionFactory expressionFactory;
private final List transformers;
private final SizeSelectInfoTransformer sizeSelectToCountTransformer;
// Mutable state
protected Class resultType;
protected int firstResult = 0;
protected int maxResults = Integer.MAX_VALUE;
protected boolean fromClassExplicitelySet = false;
private boolean needsCheck = true;
private boolean implicitJoinsApplied = false;
// Cache
protected String cachedQueryString;
protected String cachedCteQueryString;
protected boolean hasGroupBy = false;
/**
* Create flat copy of builder
*
* @param builder
*/
@SuppressWarnings("unchecked")
protected AbstractCommonQueryBuilder(AbstractCommonQueryBuilder 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) builder.whereManager;
this.havingManager = (HavingManager) builder.havingManager;
this.groupByManager = builder.groupByManager;
this.keysetManager = builder.keysetManager;
this.cteManager = builder.cteManager;
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.aliasManager = builder.aliasManager;
this.expressionFactory = builder.expressionFactory;
this.transformers = builder.transformers;
this.resultType = builder.resultType;
this.sizeSelectToCountTransformer = builder.sizeSelectToCountTransformer;
}
protected AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class resultClazz, String alias, AliasManager aliasManager, JoinManager parentJoinManager, ExpressionFactory expressionFactory, FinalSetReturn finalSetOperationBuilder) {
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.cteManager = mainQuery.cteManager;
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, jpaProvider, registeredFunctions);
this.joinManager = new JoinManager(mainQuery, queryGenerator, this.aliasManager, parentJoinManager, expressionFactory);
// set defaults
if (alias == null) {
alias = resultClazz.getSimpleName().toLowerCase();
} else {
// If the user supplies an alias, the intention is clear
fromClassExplicitelySet = true;
}
try {
this.joinManager.addRoot(em.getMetamodel().entity(resultClazz), alias);
} catch (IllegalArgumentException ex) {
// 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!");
}
}
this.subqueryInitFactory = new SubqueryInitiatorFactory(mainQuery, this.aliasManager, joinManager);
this.joinManager.setSubqueryInitFactory(subqueryInitFactory);
this.whereManager = new WhereManager(queryGenerator, parameterManager, subqueryInitFactory, expressionFactory);
this.havingManager = new HavingManager(queryGenerator, parameterManager, subqueryInitFactory, expressionFactory);
this.groupByManager = new GroupByManager(queryGenerator, parameterManager);
this.selectManager = new SelectManager(queryGenerator, parameterManager, this.joinManager, this.aliasManager, subqueryInitFactory, expressionFactory, jpaProvider, resultClazz);
this.orderByManager = new OrderByManager(queryGenerator, parameterManager, this.aliasManager, jpaProvider);
this.keysetManager = new KeysetManager(queryGenerator, parameterManager);
final SizeTransformationVisitor sizeTransformationVisitor = new SizeTransformationVisitor(mainQuery, this.aliasManager, subqueryInitFactory, joinManager, groupByManager, dbmsDialect);
this.transformers = Arrays.asList(new OuterFunctionTransformer(joinManager), new SubqueryRecursiveExpressionTransformer(), new SizeExpressionTransformer(sizeTransformationVisitor, selectManager));
this.sizeSelectToCountTransformer = new SizeSelectInfoTransformer(sizeTransformationVisitor, orderByManager, selectManager);
this.resultType = resultClazz;
this.finalSetOperationBuilder = finalSetOperationBuilder;
}
public AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class resultClazz, String alias, FinalSetReturn finalSetOperationBuilder) {
this(mainQuery, isMainQuery, statementType, resultClazz, alias, null, null, mainQuery.cbf.getExpressionFactory(), finalSetOperationBuilder);
}
public AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, DbmsStatementType statementType, Class resultClazz, String alias) {
this(mainQuery, isMainQuery, statementType, resultClazz, alias, null);
}
public CriteriaBuilderFactory getCriteriaBuilderFactory() {
return cbf;
}
@SuppressWarnings("unchecked")
public T getService(Class serviceClass) {
if (CriteriaBuilderFactory.class.equals(serviceClass)) {
return (T) cbf;
} else if (EntityManager.class.equals(serviceClass)) {
return (T) em;
} else if (DbmsDialect.class.equals(serviceClass)) {
return (T) dbmsDialect;
} else if (ExpressionFactory.class.isAssignableFrom(serviceClass)) {
return (T) cbf.getExpressionFactory();
}
return null;
}
@SuppressWarnings("unchecked")
public BuilderType setProperty(String propertyName, String propertyValue) {
this.mainQuery.properties.put(propertyName, propertyValue);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setProperties(Map properties) {
this.mainQuery.properties.clear();
this.mainQuery.properties.putAll(properties);
return (BuilderType) this;
}
public Map getProperties() {
return this.mainQuery.properties;
}
@SuppressWarnings("unchecked")
public StartOngoingSetOperationCTECriteriaBuilder> withStartSet(Class> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
return cteManager.withStartSet(cteClass, (BuilderType) this);
}
@SuppressWarnings("unchecked")
public FullSelectCTECriteriaBuilder with(Class> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
return cteManager.with(cteClass, (BuilderType) this);
}
@SuppressWarnings("unchecked")
public SelectRecursiveCTECriteriaBuilder withRecursive(Class> cteClass) {
if (!dbmsDialect.supportsWithClause()) {
throw new UnsupportedOperationException("The database does not support the with clause!");
}
return cteManager.withRecursive(cteClass, (BuilderType) this);
}
@SuppressWarnings("unchecked")
public ReturningModificationCriteriaBuilderFactory 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!");
}
return 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) {
FinalSetReturn finalSetOperationBuilder = this.finalSetOperationBuilder;
if (finalSetOperationBuilder == null) {
finalSetOperationBuilder = createFinalSetOperationBuilder(type, false);
finalSetOperationBuilder.setOperationManager.setStartQueryBuilder(this);
} 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) {
FinalSetReturn parentFinalSetOperationBuilder = this.finalSetOperationBuilder;
if (parentFinalSetOperationBuilder == null) {
parentFinalSetOperationBuilder = createFinalSetOperationBuilder(type, false);
parentFinalSetOperationBuilder.setOperationManager.setStartQueryBuilder(this);
} 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(Class> clazz) {
return from(clazz, clazz.getSimpleName().toLowerCase());
}
public BuilderType from(Class> clazz, String alias) {
return from(clazz, alias, null);
}
public BuilderType fromCte(Class> clazz, String cteName) {
return fromCte(clazz, clazz.getSimpleName().toLowerCase());
}
public BuilderType fromCte(Class> clazz, String cteName, String alias) {
return from(clazz, alias, null);
}
public BuilderType fromOld(Class> clazz) {
return fromOld(clazz, clazz.getSimpleName().toLowerCase());
}
public BuilderType fromOld(Class> clazz, String alias) {
return from(clazz, alias, DbmsModificationState.OLD);
}
public BuilderType fromNew(Class> clazz) {
return fromNew(clazz, clazz.getSimpleName().toLowerCase());
}
public BuilderType fromNew(Class> clazz, String alias) {
return from(clazz, alias, DbmsModificationState.NEW);
}
@SuppressWarnings("unchecked")
private BuilderType from(Class> clazz, String alias, DbmsModificationState state) {
if (!fromClassExplicitelySet) {
// When from is explicitly called we have to revert the implicit root
if (joinManager.getRoots().size() > 0) {
joinManager.removeRoot();
}
}
EntityType> type = em.getMetamodel().entity(clazz);
String finalAlias = joinManager.addRoot(type, alias);
fromClassExplicitelySet = true;
// Handle old an new references
if (state != null) {
Map versionEntities = explicitVersionEntities.get(clazz);
if (versionEntities == null) {
versionEntities = new HashMap(1);
explicitVersionEntities.put(clazz, versionEntities);
}
versionEntities.put(finalAlias, state);
}
return (BuilderType) this;
}
@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 Metamodel getMetamodel() {
return em.getMetamodel();
}
void parameterizeQuery(Query q) {
for (Parameter> p : q.getParameters()) {
// Allow to defer setting parameter values
// if (!isParameterSet(p.getName())) {
// throw new IllegalStateException("Unsatisfied parameter " + p.getName());
// }
Object paramValue = parameterManager.getParameterValue(p.getName());
if (paramValue instanceof ParameterManager.TemporalCalendarParameterWrapper) {
ParameterManager.TemporalCalendarParameterWrapper wrappedValue = (ParameterManager.TemporalCalendarParameterWrapper) paramValue;
q.setParameter(p.getName(), wrappedValue.getValue(), wrappedValue.getType());
} else if (paramValue instanceof ParameterManager.TemporalDateParameterWrapper) {
ParameterManager.TemporalDateParameterWrapper wrappedValue = (ParameterManager.TemporalDateParameterWrapper) paramValue;
q.setParameter(p.getName(), wrappedValue.getValue(), wrappedValue.getType());
} else {
q.setParameter(p.getName(), paramValue);
}
}
}
@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, new ParameterManager.TemporalCalendarParameterWrapper(value, temporalType));
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public BuilderType setParameter(String name, Date value, TemporalType temporalType) {
parameterManager.satisfyParameter(name, new ParameterManager.TemporalDateParameterWrapper(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);
}
/*
* Select methods
*/
@SuppressWarnings("unchecked")
public BuilderType distinct() {
clearCache();
selectManager.distinct();
return (BuilderType) this;
}
public CaseWhenStarterBuilder selectCase() {
return selectCase(null);
}
/* CASE (WHEN condition THEN scalarExpression)+ ELSE scalarExpression END */
@SuppressWarnings("unchecked")
public CaseWhenStarterBuilder selectCase(String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
return selectManager.selectCase((BuilderType) this, selectAlias);
}
public SimpleCaseWhenStarterBuilder selectSimpleCase(String expression) {
return selectSimpleCase(expression, null);
}
/* CASE caseOperand (WHEN scalarExpression THEN scalarExpression)+ ELSE scalarExpression END */
@SuppressWarnings("unchecked")
public SimpleCaseWhenStarterBuilder selectSimpleCase(String caseOperandExpression, String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
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);
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
verifyBuilderEnded();
selectManager.select(expr, selectAlias);
if (selectManager.getSelectInfos().size() > 1) {
// TODO: don't know if we should override this here
resultType = (Class) Tuple.class;
}
return (BuilderType) this;
}
public SubqueryInitiator selectSubquery() {
return selectSubquery(null);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator selectSubquery(String selectAlias) {
if (selectAlias != null && selectAlias.isEmpty()) {
throw new IllegalArgumentException("selectAlias");
}
verifyBuilderEnded();
return selectManager.selectSubquery((BuilderType) this, selectAlias);
}
public SubqueryInitiator selectSubquery(String subqueryAlias, String expression) {
return selectSubquery(subqueryAlias, expression, null);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator 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();
return selectManager.selectSubquery((BuilderType) this, subqueryAlias, expressionFactory.createSimpleExpression(expression), selectAlias);
}
/*
* Where methods
*/
public RestrictionBuilder where(String expression) {
Expression expr = expressionFactory.createSimpleExpression(expression);
return whereManager.restrict(this, expr);
}
/*
* Where methods
*/
public CaseWhenStarterBuilder> whereCase() {
return whereManager.restrictCase(this);
}
public SimpleCaseWhenStarterBuilder> whereSimpleCase(String expression) {
return whereManager.restrictSimpleCase(this, expressionFactory.createCaseOperandExpression(expression));
}
public WhereOrBuilder whereOr() {
return whereManager.whereOr(this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator whereExists() {
return whereManager.restrictExists((BuilderType) this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator whereNotExists() {
return whereManager.restrictNotExists((BuilderType) this);
}
public SubqueryInitiator> whereSubquery() {
return whereManager.restrict(this);
}
public SubqueryInitiator> whereSubquery(String subqueryAlias, String expression) {
return whereManager.restrict(this, subqueryAlias, expression);
}
/*
* 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) {
clearCache();
Expression expr;
if (isCompatibleModeEnabled()) {
expr = expressionFactory.createPathExpression(expression);
} else {
expr = expressionFactory.createSimpleExpression(expression);
if (!(expr instanceof PathExpression) && dbmsDialect.supportsComplexGroupBy()) {
throw new RuntimeException("The complex group by expression [" + expression + "] is not supported by the underlying database");
}
}
verifyBuilderEnded();
groupByManager.groupBy(expr);
return (BuilderType) this;
}
/*
* Having methods
*/
public RestrictionBuilder having(String expression) {
clearCache();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
Expression expr = expressionFactory.createSimpleExpression(expression);
return havingManager.restrict(this, expr);
}
public CaseWhenStarterBuilder> havingCase() {
return havingManager.restrictCase(this);
}
public SimpleCaseWhenStarterBuilder> havingSimpleCase(String expression) {
return havingManager.restrictSimpleCase(this, expressionFactory.createCaseOperandExpression(expression));
}
public HavingOrBuilder havingOr() {
clearCache();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.havingOr(this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator havingExists() {
clearCache();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictExists((BuilderType) this);
}
@SuppressWarnings("unchecked")
public SubqueryInitiator havingNotExists() {
clearCache();
if (groupByManager.isEmpty()) {
throw new IllegalStateException("Having without group by");
}
return havingManager.restrictNotExists((BuilderType) this);
}
public SubqueryInitiator> havingSubquery() {
clearCache();
return havingManager.restrict(this);
}
public SubqueryInitiator> havingSubquery(String subqueryAlias, String expression) {
clearCache();
return havingManager.restrict(this, subqueryAlias, expression);
}
/*
* 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 (isCompatibleModeEnabled()) {
expr = expressionFactory.createOrderByExpression(expression);
} else {
expr = expressionFactory.createSimpleExpression(expression);
}
_orderBy(expr, ascending, nullFirst);
return (BuilderType) this;
}
protected void verifyBuilderEnded() {
if (isMainQuery) {
cteManager.verifyBuilderEnded();
}
whereManager.verifyBuilderEnded();
keysetManager.verifyBuilderEnded();
havingManager.verifyBuilderEnded();
selectManager.verifyBuilderEnded();
joinManager.verifyBuilderEnded();
}
public void _orderBy(Expression expression, boolean ascending, boolean nullFirst) {
clearCache();
verifyBuilderEnded();
orderByManager.orderBy(expression, ascending, nullFirst);
}
/*
* 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) {
clearCache();
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) {
clearCache();
checkJoinPreconditions(path, alias, type);
joinManager.join(path, alias, type, false, true);
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public JoinOnBuilder joinOn(String path, String alias, JoinType type) {
clearCache();
checkJoinPreconditions(path, alias, type);
return joinManager.joinOn((BuilderType) this, path, alias, type, false);
}
@SuppressWarnings("unchecked")
public JoinOnBuilder joinDefaultOn(String path, String alias, JoinType type) {
clearCache();
checkJoinPreconditions(path, alias, type);
return joinManager.joinOn((BuilderType) this, path, alias, type, true);
}
public JoinOnBuilder innerJoinOn(String path, String alias) {
return joinOn(path, alias, JoinType.INNER);
}
public JoinOnBuilder innerJoinDefaultOn(String path, String alias) {
return joinDefaultOn(path, alias, JoinType.INNER);
}
public JoinOnBuilder leftJoinOn(String path, String alias) {
return joinOn(path, alias, JoinType.LEFT);
}
public JoinOnBuilder leftJoinDefaultOn(String path, String alias) {
return joinDefaultOn(path, alias, JoinType.LEFT);
}
public JoinOnBuilder rightJoinOn(String path, String alias) {
return joinOn(path, alias, JoinType.RIGHT);
}
public JoinOnBuilder rightJoinDefaultOn(String path, String alias) {
return joinDefaultOn(path, 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(joinManager);
final JoinNodeVisitor joinNodeVisitor = new OnClauseJoinNodeVisitor(joinVisitor) {
@Override
public void visit(JoinNode node) {
super.visit(node);
node.registerDependencies();
}
};
joinVisitor.setFromClause(null);
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);
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);
}
protected void applySizeSelectTransformer() {
if (selectManager.containsSizeSelect()) {
selectManager.applySelectInfoTransformer(sizeSelectToCountTransformer);
}
}
protected void applyExpressionTransformers() {
// run through expressions
// for each arrayExpression, look up the alias in the joinManager's aliasMap
// do the transformation using the alias
// exchange old arrayExpression with new PathExpression
// introduce applyTransformer method in managers
// transformer has a method that returns the transformed Expression
// the applyTransformer method will replace the transformed expression with the original one
// Problem we must have the complete (i.e. including array indices) absolute path available during array transformation of a
// path expression
// since the path expression might not be based on the root node
// we must track absolute paths to detect redundancies
// However, the absolute path in the path expression's join node does not contain information about the indices so far but it
// would
// also be a wrong match to add the indices in this structure since there can be multiple indices for the same join path element
// consider d.contacts[l] and d.contacts[x], the absolute join path is d.contacts but this path occurs with two different
// indices
// So where should be store this information or from where should we retrieve it during arrayTransformation?
// i think the answer is: we can't
// d.contacts[1].localized[1]
// d.contacts contacts, contacts.localized localized
// or we remember the already transfomred path in a Set<(BaseNode, RelativePath)> - maybe this would be sufficient
// because access to the same array with two different indices has an empty result set anyway. so if we had basePaths with
// two different indices for the same array we would output the two accesses for the subpath and the access for the current path
// just once (and not once for each distinct subpath)
for (ExpressionTransformer transformer : transformers) {
joinManager.applyTransformer(transformer);
selectManager.applyTransformer(transformer);
whereManager.applyTransformer(transformer);
groupByManager.applyTransformer(transformer);
havingManager.applyTransformer(transformer);
orderByManager.applyTransformer(transformer);
}
applySizeSelectTransformer();
// After all transformations are done, we can finally check if aggregations are used
AggregateDetectionVisitor aggregateDetector = new AggregateDetectionVisitor();
hasGroupBy = groupByManager.hasGroupBys();
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(selectManager.acceptVisitor(aggregateDetector, true));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(joinManager.acceptVisitor(aggregateDetector, true));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(whereManager.acceptVisitor(aggregateDetector));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(orderByManager.acceptVisitor(aggregateDetector, true));
hasGroupBy = hasGroupBy || Boolean.TRUE.equals(havingManager.acceptVisitor(aggregateDetector));
}
public Class getResultType() {
return resultType;
}
public String getQueryString() {
prepareAndCheck();
return getCteQueryString0();
}
protected String getBaseQueryString() {
prepareAndCheck();
return getQueryString0();
}
protected String getCteQueryString() {
prepareAndCheck();
return getCteQueryString0();
}
protected TypedQuery getTypedQuery() {
// If we have no ctes, there is nothing to do
if (!isMainQuery || cteManager.getCtes().isEmpty()) {
return getTypedQuery(getBaseQueryString());
}
TypedQuery baseQuery = getTypedQuery(getBaseQueryString());
List participatingQueries = new ArrayList();
String sqlQuery = cbf.getExtendedQuerySupport().getSql(em, baseQuery);
StringBuilder sqlSb = new StringBuilder(sqlQuery);
StringBuilder withClause = applyCtes(sqlSb, baseQuery, false, participatingQueries);
applyExtendedSql(sqlSb, false, false, withClause, null, null);
String finalQuery = sqlSb.toString();
participatingQueries.add(baseQuery);
TypedQuery query = new CustomSQLTypedQuery(participatingQueries, baseQuery, (CommonQueryBuilder>) this, cbf.getExtendedQuerySupport(), finalQuery);
// TODO: needs tests
if (selectManager.getSelectObjectBuilder() != null) {
query = transformQuery(query);
}
return query;
}
protected Query getQuery() {
return getTypedQuery();
}
protected Query getQuery(Map includedModificationStates) {
return getQuery();
}
@SuppressWarnings("unchecked")
protected TypedQuery getTypedQuery(String queryString) {
TypedQuery query = (TypedQuery) em.createQuery(queryString, selectManager.getExpectedQueryResultType());
if (firstResult != 0) {
query.setFirstResult(firstResult);
}
if (maxResults != Integer.MAX_VALUE) {
query.setMaxResults(maxResults);
}
if (selectManager.getSelectObjectBuilder() != null) {
query = transformQuery(query);
}
parameterizeQuery(query);
return query;
}
@SuppressWarnings("unchecked")
public KeysetBuilder beforeKeyset() {
clearCache();
return keysetManager.startBuilder(new KeysetBuilderImpl((BuilderType) this, keysetManager, KeysetMode.PREVIOUS));
}
public BuilderType beforeKeyset(Serializable... values) {
return beforeKeyset(new KeysetImpl(values));
}
@SuppressWarnings("unchecked")
public BuilderType beforeKeyset(Keyset keyset) {
clearCache();
keysetManager.verifyBuilderEnded();
keysetManager.setKeysetLink(new SimpleKeysetLink(keyset, KeysetMode.PREVIOUS));
return (BuilderType) this;
}
@SuppressWarnings("unchecked")
public KeysetBuilder afterKeyset() {
clearCache();
return keysetManager.startBuilder(new KeysetBuilderImpl((BuilderType) this, keysetManager, KeysetMode.NEXT));
}
public BuilderType afterKeyset(Serializable... values) {
return afterKeyset(new KeysetImpl(values));
}
@SuppressWarnings("unchecked")
public BuilderType afterKeyset(Keyset keyset) {
clearCache();
keysetManager.verifyBuilderEnded();
keysetManager.setKeysetLink(new SimpleKeysetLink(keyset, KeysetMode.NEXT));
return (BuilderType) this;
}
protected String getQueryString0() {
if (cachedQueryString == null) {
cachedQueryString = getQueryString1();
}
return cachedQueryString;
}
protected String getCteQueryString0() {
if (cachedCteQueryString == null) {
cachedCteQueryString = getCteQueryString1();
}
return cachedCteQueryString;
}
protected void clearCache() {
needsCheck = true;
cachedQueryString = null;
cachedCteQueryString = null;
implicitJoinsApplied = false;
}
protected void prepareAndCheck() {
if (!needsCheck) {
return;
}
verifyBuilderEnded();
// 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
Metamodel m = em.getMetamodel();
List orderByExpressions = orderByManager.getOrderByExpressions(m);
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 getQueryString1() {
StringBuilder sbSelectFrom = new StringBuilder();
getQueryString1(sbSelectFrom);
return sbSelectFrom.toString();
}
protected void getQueryString1(StringBuilder sbSelectFrom) {
appendSelectClause(sbSelectFrom);
appendFromClause(sbSelectFrom);
appendWhereClause(sbSelectFrom);
appendGroupByClause(sbSelectFrom);
appendOrderByClause(sbSelectFrom);
}
protected String getCteQueryString1() {
StringBuilder sbSelectFrom = new StringBuilder();
getCteQueryString1(sbSelectFrom);
return sbSelectFrom.toString();
}
protected void getCteQueryString1(StringBuilder sbSelectFrom) {
if (isMainQuery) {
cteManager.buildClause(sbSelectFrom);
}
getQueryString1(sbSelectFrom);
}
protected void appendSelectClause(StringBuilder sbSelectFrom) {
selectManager.buildSelect(sbSelectFrom);
}
protected void appendFromClause(StringBuilder sbSelectFrom) {
joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null);
}
protected void appendWhereClause(StringBuilder sbSelectFrom) {
KeysetLink keysetLink = keysetManager.getKeysetLink();
if (keysetLink == null || keysetLink.getKeysetMode() == KeysetMode.NONE) {
whereManager.buildClause(sbSelectFrom);
} else {
sbSelectFrom.append(" WHERE ");
keysetManager.buildKeysetPredicate(sbSelectFrom);
if (whereManager.hasPredicates()) {
sbSelectFrom.append(" AND ");
whereManager.buildClausePredicate(sbSelectFrom);
}
}
}
protected void appendGroupByClause(StringBuilder sbSelectFrom) {
Set clauses = new LinkedHashSet();
groupByManager.buildGroupByClauses(clauses);
if (hasGroupBy) {
if (isImplicitGroupByFromSelect()) {
selectManager.buildGroupByClauses(em.getMetamodel(), clauses);
}
if (isImplicitGroupByFromHaving()) {
havingManager.buildGroupByClauses(clauses);
}
if (isImplicitGroupByFromOrderBy()) {
orderByManager.buildGroupByClauses(clauses);
}
}
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 getModificationStates(Map, Map> explicitVersionEntities) {
return null;
}
protected Map getModificationStateRelatedTableNameRemappings(Map, Map> explicitVersionEntities) {
return null;
}
private boolean applyAddedCtes(Query query, AbstractCommonQueryBuilder, ?, ?, ?, ?> queryBuilder, StringBuilder sb, Map tableNameRemapping, boolean firstCte) {
if (query instanceof CustomSQLQuery) {
// EntityAlias -> CteName
Map cteTableNameRemappings = queryBuilder.getModificationStateRelatedTableNameRemappings(explicitVersionEntities);
// CteName -> CteQueryString
Map addedCtes = ((CustomSQLQuery) query).getAddedCtes();
if (addedCtes != null && addedCtes.size() > 0) {
for (Map.Entry simpleCteEntry : addedCtes.entrySet()) {
for (Map.Entry cteTableNameRemapping : cteTableNameRemappings.entrySet()) {
if (cteTableNameRemapping.getValue().equals(simpleCteEntry.getKey())) {
tableNameRemapping.put(cteTableNameRemapping.getKey(), cteTableNameRemapping.getValue());
}
}
if (firstCte) {
firstCte = false;
} else {
sb.append(",\n");
}
sb.append(simpleCteEntry.getKey());
sb.append(" AS (\n");
sb.append(simpleCteEntry.getValue());
sb.append("\n)");
}
}
}
return firstCte;
}
protected StringBuilder applyCtes(StringBuilder sqlSb, Query baseQuery, boolean isSubquery, List participatingQueries) {
// NOTE: Delete statements could cause CTEs to be generated for the cascading deletes
if (!isMainQuery || isSubquery || !cteManager.hasCtes() && statementType != DbmsStatementType.DELETE) {
return null;
}
// EntityAlias -> CteName
Map tableNameRemapping = new LinkedHashMap(0);
StringBuilder sb = new StringBuilder(cteManager.getCtes().size() * 100);
sb.append(dbmsDialect.getWithClause(cteManager.isRecursive()));
sb.append(" ");
boolean firstCte = true;
for (CTEInfo cteInfo : cteManager.getCtes()) {
// Build queries and add as participating queries
Map modificationStates = cteInfo.nonRecursiveCriteriaBuilder.getModificationStates(explicitVersionEntities);
Query nonRecursiveQuery = cteInfo.nonRecursiveCriteriaBuilder.getQuery(modificationStates);
participatingQueries.add(nonRecursiveQuery);
Query recursiveQuery = null;
if (cteInfo.recursive) {
modificationStates = cteInfo.nonRecursiveCriteriaBuilder.getModificationStates(explicitVersionEntities);
recursiveQuery = cteInfo.recursiveCriteriaBuilder.getQuery(modificationStates);
participatingQueries.add(recursiveQuery);
}
// add cascading delete statements as CTEs
firstCte = applyCascadingDelete(nonRecursiveQuery, cteInfo.nonRecursiveCriteriaBuilder, participatingQueries, sb, cteInfo.name, firstCte);
firstCte = applyAddedCtes(nonRecursiveQuery, cteInfo.nonRecursiveCriteriaBuilder, sb, tableNameRemapping, firstCte);
firstCte = applyAddedCtes(recursiveQuery, cteInfo.recursiveCriteriaBuilder, sb, tableNameRemapping, firstCte);
String cteNonRecursiveSqlQuery = getSql(nonRecursiveQuery);
if (firstCte) {
firstCte = false;
} else {
sb.append(",\n");
}
String cteName = cteInfo.cteType.getName();
sb.append(cteName);
sb.append('(');
final List attributes = cteInfo.attributes;
boolean first = true;
for (int i = 0; i < attributes.size(); i++) {
String[] columns = cbf.getExtendedQuerySupport().getColumnNames(em, cteInfo.cteType, attributes.get(i));
for (String column : columns) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(column);
}
}
sb.append(')');
sb.append(" AS(\n");
sb.append(cteNonRecursiveSqlQuery);
if (cteInfo.recursive) {
String cteRecursiveSqlQuery = getSql(recursiveQuery);
if (cteInfo.unionAll) {
sb.append("\nUNION ALL\n");
} else {
sb.append("\nUNION\n");
}
sb.append(cteRecursiveSqlQuery);
} else if (!dbmsDialect.supportsNonRecursiveWithClause()) {
sb.append("\nUNION ALL\n");
sb.append("SELECT ");
sb.append("NULL");
for (int i = 1; i < attributes.size(); i++) {
sb.append(", ");
sb.append("NULL");
}
sb.append(" FROM DUAL WHERE 1=0");
}
sb.append("\n)");
}
// Add cascading delete statements from base query as CTEs
firstCte = applyCascadingDelete(baseQuery, this, participatingQueries, sb, "main_query", firstCte);
// If no CTE has been added, we can just return
if (firstCte) {
return null;
}
for (CTEInfo cteInfo : cteManager.getCtes()) {
String cteName = cteInfo.cteType.getName();
// TODO: this is a hibernate specific integration detail
// Replace the subview subselect that is generated for this cte
final String subselect = "( select * from " + cteName + " )";
int subselectIndex = 0;
while ((subselectIndex = sb.indexOf(subselect, subselectIndex)) > -1) {
sb.replace(subselectIndex, subselectIndex + subselect.length(), cteName);
}
final String mainSubselect = "( select * from " + cteName + " )";
subselectIndex = 0;
while ((subselectIndex = sqlSb.indexOf(mainSubselect, subselectIndex)) > -1) {
sqlSb.replace(subselectIndex, subselectIndex + mainSubselect.length(), cteName);
}
}
sb.append("\n");
for (Map.Entry tableNameRemappingEntry : tableNameRemapping.entrySet()) {
String sqlAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, tableNameRemappingEntry.getKey());
String newCteName = tableNameRemappingEntry.getValue();
applyTableNameRemapping(sqlSb, sqlAlias, newCteName);
}
return sb;
}
private boolean applyCascadingDelete(Query baseQuery, AbstractCommonQueryBuilder, ?, ?, ?, ?> queryBuilder, List participatingQueries, StringBuilder sb, String cteBaseName, boolean firstCte) {
if (queryBuilder.statementType == DbmsStatementType.DELETE) {
List cascadingDeleteSqls = cbf.getExtendedQuerySupport().getCascadingDeleteSql(em, baseQuery);
StringBuilder cascadingDeleteSqlSb = new StringBuilder();
int cteBaseNameCount = 0;
for (String cascadingDeleteSql : cascadingDeleteSqls) {
if (firstCte) {
firstCte = false;
} else {
sb.append(",\n");
}
// Since we kind of need the parameters from the base query, it will participate for each cascade
participatingQueries.add(baseQuery);
sb.append(cteBaseName);
sb.append('_').append(cteBaseNameCount);
sb.append(" AS (\n");
cascadingDeleteSqlSb.setLength(0);
cascadingDeleteSqlSb.append(cascadingDeleteSql);
dbmsDialect.appendExtendedSql(cascadingDeleteSqlSb, DbmsStatementType.DELETE, false, true, null, null, null, null, null);
sb.append(cascadingDeleteSqlSb);
sb.append("\n)");
}
}
return firstCte;
}
private void applyTableNameRemapping(StringBuilder sb, String sqlAlias, String newCteName) {
final String searchAs = " as";
final String searchAlias = " " + sqlAlias;
int searchIndex = 0;
while ((searchIndex = sb.indexOf(searchAlias, searchIndex)) > -1) {
char c = sb.charAt(searchIndex + searchAlias.length());
if (c == '.') {
// This is a dereference of the alias, skip this
} else {
int[] indexRange;
if (searchAs.equalsIgnoreCase(sb.substring(searchIndex - searchAs.length(), searchIndex))) {
// Uses aliasing with the AS keyword
indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length());
} else {
// Uses aliasing without the AS keyword
indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex);
}
int oldLength = indexRange[1] - indexRange[0];
// Replace table name with cte name
sb.replace(indexRange[0], indexRange[1], newCteName);
// Adjust index after replacing
searchIndex += newCteName.length() - oldLength;
}
searchIndex = searchIndex + 1;
}
}
private int[] rtrimBackwardsToFirstWhitespace(StringBuilder sb, int startIndex) {
int tableNameStartIndex;
int tableNameEndIndex = startIndex;
boolean text = false;
for (tableNameStartIndex = tableNameEndIndex; tableNameStartIndex >= 0; tableNameStartIndex--) {
if (text) {
final char c = sb.charAt(tableNameStartIndex);
if (Character.isWhitespace(c) || c == ',') {
tableNameStartIndex++;
break;
}
} else {
if (Character.isWhitespace(sb.charAt(tableNameStartIndex))) {
tableNameEndIndex--;
} else {
text = true;
tableNameEndIndex++;
}
}
}
return new int[]{ tableNameStartIndex, tableNameEndIndex };
}
private String getSql(Query query) {
if (query instanceof CustomSQLQuery) {
return ((CustomSQLQuery) query).getSql();
} else if (query instanceof CustomSQLTypedQuery>) {
return ((CustomSQLTypedQuery>) query).getSql();
}
return cbf.getExtendedQuerySupport().getSql(em, query);
}
protected boolean hasLimit() {
return firstResult != 0 || maxResults != Integer.MAX_VALUE;
}
protected Map applyExtendedSql(StringBuilder sqlSb, boolean isSubquery, boolean isEmbedded, StringBuilder withClause, String[] returningColumns, Map includedModificationStates) {
String limit = null;
String offset = null;
if (firstResult != 0) {
offset = Integer.toString(firstResult);
}
if (maxResults != Integer.MAX_VALUE) {
limit = Integer.toString(maxResults);
}
return dbmsDialect.appendExtendedSql(sqlSb, statementType, isSubquery, isEmbedded, withClause, limit, offset, returningColumns, includedModificationStates);
}
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 TypedQuery transformQuery(TypedQuery query) {
TypedQuery currentQuery = query;
for (QueryTransformer transformer : cbf.getQueryTransformers()) {
currentQuery = (TypedQuery) transformer.transformQuery(query, selectManager.getSelectObjectBuilder());
}
return currentQuery;
}
private boolean isCompatibleModeEnabled() {
return PropertyUtils.getAsBooleanProperty(mainQuery.properties, ConfigurationProperties.COMPATIBLE_MODE, false);
}
private boolean isImplicitGroupByFromSelect() {
return PropertyUtils.getAsBooleanProperty(mainQuery.properties, ConfigurationProperties.IMPLICIT_GROUP_BY_FROM_SELECT, true);
}
private boolean isImplicitGroupByFromHaving() {
return PropertyUtils.getAsBooleanProperty(mainQuery.properties, ConfigurationProperties.IMPLICIT_GROUP_BY_FROM_HAVING, true);
}
private boolean isImplicitGroupByFromOrderBy() {
return PropertyUtils.getAsBooleanProperty(mainQuery.properties, ConfigurationProperties.IMPLICIT_GROUP_BY_FROM_ORDER_BY, true);
}
// TODO: needs equals-hashCode implementation
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy