Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.blazebit.persistence.impl.BaseUpdateCriteriaBuilderImpl Maven / Gradle / Ivy
/*
* Copyright 2014 - 2020 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.BaseUpdateCriteriaBuilder;
import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.MultipleSubqueryInitiator;
import com.blazebit.persistence.ReturningBuilder;
import com.blazebit.persistence.ReturningObjectBuilder;
import com.blazebit.persistence.ReturningResult;
import com.blazebit.persistence.SubqueryBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.impl.builder.expression.ExpressionBuilder;
import com.blazebit.persistence.impl.builder.expression.ExpressionBuilderEndedListener;
import com.blazebit.persistence.impl.function.entity.ValuesEntity;
import com.blazebit.persistence.impl.query.CTENode;
import com.blazebit.persistence.impl.query.CustomReturningSQLTypedQuery;
import com.blazebit.persistence.impl.query.CustomSQLQuery;
import com.blazebit.persistence.impl.query.QuerySpecification;
import com.blazebit.persistence.impl.query.UpdateModificationQuerySpecification;
import com.blazebit.persistence.impl.util.SqlUtils;
import com.blazebit.persistence.parser.SimpleQueryGenerator;
import com.blazebit.persistence.parser.expression.Expression;
import com.blazebit.persistence.parser.expression.ExpressionCopyContext;
import com.blazebit.persistence.parser.expression.SubqueryExpression;
import com.blazebit.persistence.spi.DbmsModificationState;
import com.blazebit.persistence.spi.DbmsStatementType;
import com.blazebit.persistence.spi.ExtendedManagedType;
import com.blazebit.persistence.spi.ExtendedQuerySupport;
import com.blazebit.persistence.spi.JpaMetamodelAccessor;
import com.blazebit.persistence.spi.UpdateJoinStyle;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
/**
*
* @param The query result type
* @author Christian Beikov
* @since 1.1.0
*/
public abstract class BaseUpdateCriteriaBuilderImpl, Y> extends AbstractModificationCriteriaBuilder implements BaseUpdateCriteriaBuilder, SubqueryBuilderListener, ExpressionBuilderEndedListener {
protected final Map setAttributeBindingMap = new LinkedHashMap<>();
private SubqueryInternalBuilder currentSubqueryBuilder;
private String currentAttribute;
public BaseUpdateCriteriaBuilderImpl(MainQuery mainQuery, QueryContext queryContext, boolean isMainQuery, Class clazz, String alias, CTEManager.CTEKey cteKey, Class cteClass, Y result, CTEBuilderListener listener) {
super(mainQuery, queryContext, isMainQuery, DbmsStatementType.UPDATE, clazz, alias, cteKey, cteClass, result, listener);
}
public BaseUpdateCriteriaBuilderImpl(BaseUpdateCriteriaBuilderImpl builder, MainQuery mainQuery, QueryContext queryContext, Map joinManagerMapping, ExpressionCopyContext copyContext) {
super(builder, mainQuery, queryContext, joinManagerMapping, copyContext);
builder.verifyBuilderEnded();
for (Entry entry : builder.setAttributeBindingMap.entrySet()) {
this.setAttributeBindingMap.put(entityType.getName(), entry.getValue());
}
}
@Override
protected void collectParameters() {
ParameterRegistrationVisitor parameterRegistrationVisitor = parameterManager.getParameterRegistrationVisitor();
ClauseType oldClauseType = parameterRegistrationVisitor.getClauseType();
AbstractCommonQueryBuilder oldQueryBuilder = parameterRegistrationVisitor.getQueryBuilder();
try {
parameterRegistrationVisitor.setQueryBuilder(this);
parameterRegistrationVisitor.setClauseType(ClauseType.SET);
selectManager.acceptVisitor(parameterRegistrationVisitor);
parameterRegistrationVisitor.setClauseType(ClauseType.WHERE);
whereManager.acceptVisitor(parameterRegistrationVisitor);
} finally {
parameterRegistrationVisitor.setClauseType(oldClauseType);
parameterRegistrationVisitor.setQueryBuilder(oldQueryBuilder);
}
}
@Override
@SuppressWarnings("unchecked")
public X set(String attributeName, Object value) {
verifyBuilderEnded();
addAttribute(attributeName);
Expression attributeExpression = parameterManager.addParameterExpression(value, ClauseType.SET, this);
selectManager.select(attributeExpression, null);
return (X) this;
}
@Override
@SuppressWarnings("unchecked")
public X setNull(String attributeName) {
return setExpression(attributeName, "NULL");
}
@Override
@SuppressWarnings("unchecked")
public X setExpression(String attributeName, String expression) {
verifyBuilderEnded();
addAttribute(attributeName);
Expression attributeExpression = expressionFactory.createSimpleExpression(expression, false);
parameterManager.collectParameterRegistrations(attributeExpression, ClauseType.SET, this);
selectManager.select(attributeExpression, null);
return (X) this;
}
@SuppressWarnings("unchecked")
@Override
public SubqueryInitiator set(String attribute) {
verifySubqueryBuilderEnded();
addAttribute(attribute);
this.currentAttribute = attribute;
return subqueryInitFactory.createSubqueryInitiator((X) this, this, false, ClauseType.SET);
}
@SuppressWarnings("unchecked")
@Override
public MultipleSubqueryInitiator setSubqueries(String attribute, String expression) {
verifySubqueryBuilderEnded();
addAttribute(attribute);
this.currentAttribute = attribute;
Expression expr = expressionFactory.createSimpleExpression(expression, true);
MultipleSubqueryInitiator initiator = new MultipleSubqueryInitiatorImpl((X) this, expr, this, subqueryInitFactory, ClauseType.SET);
return initiator;
}
@Override
public SubqueryBuilder set(String attribute, FullQueryBuilder criteriaBuilder) {
verifySubqueryBuilderEnded();
addAttribute(attribute);
this.currentAttribute = attribute;
return subqueryInitFactory.createSubqueryBuilder((X) this, this, false, criteriaBuilder, ClauseType.SET);
}
private void verifySubqueryBuilderEnded() {
if (currentAttribute != null) {
throw new BuilderChainingException("An initiator was not ended properly.");
}
if (currentSubqueryBuilder != null) {
throw new BuilderChainingException("An subquery builder was not ended properly.");
}
}
@Override
public void onBuilderEnded(ExpressionBuilder builder) {
if (currentAttribute == null) {
throw new BuilderChainingException("There was an attempt to end a builder that was not started or already closed.");
}
Expression attributeExpression = builder.getExpression();
parameterManager.collectParameterRegistrations(attributeExpression, ClauseType.SET, this);
selectManager.select(attributeExpression, null);
}
@Override
public void onBuilderEnded(SubqueryInternalBuilder builder) {
if (currentAttribute == null) {
throw new BuilderChainingException("There was an attempt to end a builder that was not started or already closed.");
}
if (currentSubqueryBuilder == null) {
throw new BuilderChainingException("There was an attempt to end a builder that was not started or already closed.");
}
Expression attributeExpression = new SubqueryExpression(builder);
parameterManager.collectParameterRegistrations(attributeExpression, ClauseType.SET, this);
selectManager.select(attributeExpression, null);
currentAttribute = null;
currentSubqueryBuilder = null;
}
@Override
public void onBuilderStarted(SubqueryInternalBuilder builder) {
if (currentAttribute == null) {
throw new BuilderChainingException("There was an attempt to start a builder without an originating initiator.");
}
if (currentSubqueryBuilder != null) {
throw new BuilderChainingException("There was an attempt to start a builder but a previous builder was not ended.");
}
currentSubqueryBuilder = builder;
}
@Override
public void onReplaceBuilder(SubqueryInternalBuilder oldBuilder, SubqueryInternalBuilder newBuilder) {
throw new IllegalArgumentException("Replace not valid!");
}
@Override
public void onInitiatorStarted(SubqueryInitiator initiator) {
throw new IllegalArgumentException("Initiator started not valid!");
}
protected void addAttribute(String attributeName) {
// Just do that to assert the attribute exists
JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor();
jpaMetamodelAccessor.getBasicAttributePath(getMetamodel(), entityType, attributeName);
Integer attributeBindIndex = setAttributeBindingMap.get(attributeName);
if (attributeBindIndex != null) {
throw new IllegalArgumentException("The attribute [" + attributeName + "] has already been bound!");
}
setAttributeBindingMap.put(attributeName, selectManager.getSelectInfos().size());
}
@Override
protected void prepareSelect() {
JoinNode rootNode = joinManager.getRoots().get(0);
// We only need this when rendering a plain update statement, but not when doing SQL replacement
boolean enableElementCollectionIdCutoff = (joinManager.getRoots().size() > 1 || rootNode.hasChildNodes()) && mainQuery.dbmsDialect.getUpdateJoinStyle() != UpdateJoinStyle.NONE;
// We have an update statement here which supports parameters in SELECT/SET clause
JpaUtils.expandBindings(setAttributeBindingMap, null, mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityType).getOwnedSingularAttributes(), ClauseType.SET, this, null, enableElementCollectionIdCutoff);
}
@Override
protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation, JoinNode lateralJoinNode) {
sbSelectFrom.append("UPDATE ");
sbSelectFrom.append(entityType.getName()).append(' ');
sbSelectFrom.append(entityAlias);
appendSetClause(sbSelectFrom, externalRepresentation);
JoinNode rootNode = joinManager.getRoots().get(0);
if (joinManager.getRoots().size() > 1 || rootNode.hasChildNodes()) {
if (externalRepresentation) {
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(sbSelectFrom, Collections.emptySet(), null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, nodesToFetch, Collections.emptySet(), null, true);
appendWhereClause(sbSelectFrom, externalRepresentation);
} else {
boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation();
queryGenerator.setExternalRepresentation(externalRepresentation);
try {
if (mainQuery.dbmsDialect.getUpdateJoinStyle() == UpdateJoinStyle.NONE) {
sbSelectFrom.append(" WHERE EXISTS (SELECT 1");
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(sbSelectFrom, Collections.emptySet(), null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, nodesToFetch, Collections.emptySet(), rootNode, true);
appendWhereClause(sbSelectFrom, externalRepresentation);
sbSelectFrom.append(')');
} else {
sbSelectFrom.setLength(0);
if (mainQuery.dbmsDialect.getUpdateJoinStyle() == UpdateJoinStyle.MERGE || mainQuery.dbmsDialect.getUpdateJoinStyle() == UpdateJoinStyle.REFERENCE) {
// We have to build a query that puts the set clause expressions into the group by to align with the parameter positions in the final SQL
sbSelectFrom.append("SELECT 1");
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, nodesToFetch, Collections.emptySet(), null, true);
appendWhereClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, lateralJoinNode);
sbSelectFrom.append(" GROUP BY ");
appendSetElementsAsCaseExpressions(sbSelectFrom);
} else {
sbSelectFrom.append("SELECT ");
appendSetElementsAsCaseExpressions(sbSelectFrom);
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, nodesToFetch, Collections.emptySet(), null, true);
appendWhereClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, lateralJoinNode);
}
}
} finally {
queryGenerator.setExternalRepresentation(originalExternalRepresentation);
}
}
} else {
appendWhereClause(sbSelectFrom, externalRepresentation);
}
}
protected void appendSetElementsAsCaseExpressions(StringBuilder sbSelectFrom) {
queryGenerator.setClauseType(ClauseType.SET);
queryGenerator.setQueryBuffer(sbSelectFrom);
boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation();
queryGenerator.setExternalRepresentation(false);
SimpleQueryGenerator.BooleanLiteralRenderingContext oldBooleanLiteralRenderingContext = queryGenerator.setBooleanLiteralRenderingContext(SimpleQueryGenerator.BooleanLiteralRenderingContext.CASE_WHEN);
try {
List selectInfos = selectManager.getSelectInfos();
Iterator> setAttributeIter = setAttributeBindingMap.entrySet().iterator();
if (setAttributeIter.hasNext()) {
Map.Entry attributeEntry = setAttributeIter.next();
sbSelectFrom.append("CASE WHEN ");
appendSetElementAsSelectItem(sbSelectFrom, attributeEntry.getKey());
sbSelectFrom.append(" = ");
queryGenerator.generate(selectInfos.get(attributeEntry.getValue()).getExpression());
sbSelectFrom.append(" THEN 1 ELSE 0 END");
while (setAttributeIter.hasNext()) {
attributeEntry = setAttributeIter.next();
sbSelectFrom.append(", ");
sbSelectFrom.append("CASE WHEN ");
appendSetElementAsSelectItem(sbSelectFrom, attributeEntry.getKey());
sbSelectFrom.append(" = ");
queryGenerator.generate(selectInfos.get(attributeEntry.getValue()).getExpression());
sbSelectFrom.append(" THEN 1 ELSE 0 END");
}
}
} finally {
queryGenerator.setBooleanLiteralRenderingContext(oldBooleanLiteralRenderingContext);
queryGenerator.setClauseType(null);
queryGenerator.setExternalRepresentation(originalExternalRepresentation);
}
}
@Override
protected Query getQuery(Map includedModificationStates) {
prepareAndCheck();
JoinNode rootNode = joinManager.getRoots().get(0);
if (joinManager.getRoots().size() > 1 || rootNode.hasChildNodes()) {
// Prefer an exists subquery instead of MERGE
if (mainQuery.dbmsDialect.getUpdateJoinStyle() == UpdateJoinStyle.NONE) {
return super.getQuery(includedModificationStates);
}
Query baseQuery = em.createQuery(getBaseQueryStringWithCheck(null, null));
QuerySpecification querySpecification = getQuerySpecification(baseQuery, getCountExampleQuery(), getReturningColumns(), null, includedModificationStates);
Query query = new CustomSQLQuery(
querySpecification,
baseQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(query);
return query;
} else {
return super.getQuery(includedModificationStates);
}
}
@Override
protected TypedQuery> getExecuteWithReturningQuery(TypedQuery exampleQuery, Query baseQuery, String[] returningColumns, ReturningObjectBuilder objectBuilder) {
QuerySpecification querySpecification = getQuerySpecification(baseQuery, exampleQuery, returningColumns, objectBuilder, null);
CustomReturningSQLTypedQuery query = new CustomReturningSQLTypedQuery(
querySpecification,
exampleQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
query.setFirstResult(firstResult);
query.setMaxResults(maxResults);
parameterManager.parameterizeQuery(query);
return query;
}
private QuerySpecification getQuerySpecification(Query baseQuery, Query exampleQuery, String[] returningColumns, ReturningObjectBuilder objectBuilder, Map includedModificationStates) {
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
boolean isEmbedded = this instanceof ReturningBuilder;
boolean shouldRenderCteNodes = renderCteNodes(isEmbedded);
List ctes = shouldRenderCteNodes ? getCteNodes(isEmbedded) : Collections.EMPTY_LIST;
List setColumns = getSetColumns();
ExtendedQuerySupport extendedQuerySupport = getService(ExtendedQuerySupport.class);
Map aliasMapping = new TreeMap<>();
JoinNode rootNode = joinManager.getRoots().get(0);
String[] idColumns = null;
String tableToUpdate = null;
String tableAlias = null;
if ((joinManager.getRoots().size() > 1 || rootNode.hasChildNodes()) && mainQuery.dbmsDialect.getUpdateJoinStyle() != UpdateJoinStyle.NONE) {
String sql = getService(ExtendedQuerySupport.class).getSql(em, baseQuery);
if (SqlUtils.indexOfSelect(sql) != -1) {
idColumns = getIdColumns(getMetamodel().getManagedType(ExtendedManagedType.class, entityType));
int fromIndex = SqlUtils.indexOfFrom(sql);
int tableStartIndex = fromIndex + SqlUtils.FROM.length();
int tableEndIndex = sql.indexOf(" ", tableStartIndex);
tableToUpdate = sql.substring(tableStartIndex, tableEndIndex);
tableAlias = extendedQuerySupport.getSqlAlias(em, baseQuery, entityAlias);
for (String idColumn : idColumns) {
aliasMapping.put(tableAlias + "." + idColumn, "tmp.c" + aliasMapping.size());
}
SqlUtils.buildAliasMappingForTopLevelSelects(extendedQuerySupport.getSql(em, baseQuery), "tmp", aliasMapping);
}
}
return new UpdateModificationQuerySpecification(
this,
baseQuery,
exampleQuery,
parameterManager.getParameters(),
parameterListNames,
mainQuery.cteManager.isRecursive(),
ctes,
shouldRenderCteNodes,
isEmbedded,
returningColumns,
objectBuilder,
includedModificationStates,
returningAttributeBindingMap,
mainQuery.getQueryConfiguration().isQueryPlanCacheEnabled(),
tableToUpdate,
tableAlias,
idColumns,
setColumns,
aliasMapping,
getUpdateExampleQuery()
);
}
protected List getSetColumns() {
StringBuilder setExtractionSb = new StringBuilder();
boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation();
StringBuilder originalQueryBuffer = queryGenerator.getQueryBuffer();
queryGenerator.setExternalRepresentation(false);
queryGenerator.setQueryBuffer(setExtractionSb);
try {
setExtractionSb.append("SELECT ");
Iterator> setAttributeIter = setAttributeBindingMap.entrySet().iterator();
if (setAttributeIter.hasNext()) {
Map.Entry attributeEntry = setAttributeIter.next();
appendSetElementAsSelectItem(setExtractionSb, attributeEntry.getKey());
while (setAttributeIter.hasNext()) {
setExtractionSb.append(", ");
appendSetElementAsSelectItem(setExtractionSb, setAttributeIter.next().getKey());
}
}
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(setExtractionSb, EnumSet.noneOf(ClauseType.class), null, false, false, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, nodesToFetch, Collections.emptySet(), null, true);
appendWhereClause(setExtractionSb, whereClauseConjuncts, optionalWhereClauseConjuncts, null);
} finally {
queryGenerator.setExternalRepresentation(originalExternalRepresentation);
queryGenerator.setQueryBuffer(originalQueryBuffer);
}
String sql = getService(ExtendedQuerySupport.class).getSql(em, em.createQuery(setExtractionSb.toString()));
return Arrays.asList(SqlUtils.getSelectItems(sql, 0, new SqlUtils.SelectItemExtractor() {
@Override
public String extract(StringBuilder sb, int index, int currentPosition) {
return sb.substring(sb.indexOf(".") + 1, sb.indexOf(" ")).trim();
}
}));
}
protected void appendSetClause(StringBuilder sbSelectFrom, boolean externalRepresentation) {
sbSelectFrom.append(" SET ");
queryGenerator.setClauseType(ClauseType.SET);
queryGenerator.setQueryBuffer(sbSelectFrom);
boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation();
queryGenerator.setExternalRepresentation(externalRepresentation);
SimpleQueryGenerator.BooleanLiteralRenderingContext oldBooleanLiteralRenderingContext = queryGenerator.setBooleanLiteralRenderingContext(SimpleQueryGenerator.BooleanLiteralRenderingContext.CASE_WHEN);
try {
List selectInfos = selectManager.getSelectInfos();
Iterator> setAttributeIter = setAttributeBindingMap.entrySet().iterator();
if (setAttributeIter.hasNext()) {
Map.Entry attributeEntry = setAttributeIter.next();
appendSetElement(sbSelectFrom, attributeEntry.getKey(), selectInfos.get(attributeEntry.getValue()).getExpression());
while (setAttributeIter.hasNext()) {
attributeEntry = setAttributeIter.next();
sbSelectFrom.append(", ");
appendSetElement(sbSelectFrom, attributeEntry.getKey(), selectInfos.get(attributeEntry.getValue()).getExpression());
}
}
} finally {
queryGenerator.setBooleanLiteralRenderingContext(oldBooleanLiteralRenderingContext);
queryGenerator.setClauseType(null);
queryGenerator.setExternalRepresentation(originalExternalRepresentation);
}
}
protected final void appendSetElement(StringBuilder sbSelectFrom, String attribute, Expression valueExpression) {
String trimmedPath = attribute.trim();
if (appendSetElementEntityPrefix(trimmedPath)) {
sbSelectFrom.append(entityAlias).append('.');
}
sbSelectFrom.append(attribute);
sbSelectFrom.append(" = ");
queryGenerator.generate(valueExpression);
}
protected void appendSetElementAsSelectItem(StringBuilder sbSelectFrom, String attribute) {
String trimmedPath = attribute.trim();
if (appendSetElementEntityPrefix(trimmedPath)) {
sbSelectFrom.append(entityAlias).append('.');
}
sbSelectFrom.append(attribute);
}
protected boolean appendSetElementEntityPrefix(String trimmedPath) {
String indexStart = "index(";
String keyStart = "key(";
return !trimmedPath.regionMatches(true, 0, indexStart, 0, indexStart.length())
&& !trimmedPath.regionMatches(true, 0, keyStart, 0, keyStart.length());
}
protected Query getUpdateExampleQuery() {
// This is the query we use as "hull" to put other sqls into
// We chose ValuesEntity as update base because it is known to be non-polymorphic
// We could have used the owner entity type as well, but at the time of writing,
// it wasn't clear if problems might arise when the entity type were polymorphic
String exampleQueryString = "UPDATE " + ValuesEntity.class.getSimpleName() + " SET value = NULL";
return em.createQuery(exampleQueryString);
}
}