Make sure DML updates do inserts into secondary tables when necessary
This commit is contained in:
parent
955e8265dc
commit
20564a5547
|
@ -15,7 +15,6 @@ import java.util.Map;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
|
@ -69,7 +68,6 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
*/
|
||||
public abstract class AbstractCteMutationHandler extends AbstractMutationHandler {
|
||||
|
||||
public static final String DML_RESULT_TABLE_NAME_PREFIX = "dml_cte_";
|
||||
public static final String CTE_TABLE_IDENTIFIER = "id";
|
||||
|
||||
private final SqmCteTable cteTable;
|
||||
|
@ -126,7 +124,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
|
|||
executionContext.getQueryParameterBindings(),
|
||||
factory
|
||||
);
|
||||
final Map<SqmParameter, List<JdbcParameter>> parameterResolutions;
|
||||
final Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions;
|
||||
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
|
||||
parameterResolutions = Collections.emptyMap();
|
||||
}
|
||||
|
@ -148,7 +146,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
|
|||
MatchingIdSelectionHelper.generateMatchingIdSelectStatement(
|
||||
entityDescriptor,
|
||||
sqmMutationStatement,
|
||||
false,
|
||||
true,
|
||||
restriction,
|
||||
sqmConverter,
|
||||
executionContext,
|
||||
|
@ -242,15 +240,41 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
|
|||
CteStatement idSelectCte,
|
||||
ModelPart fkModelPart,
|
||||
SessionFactoryImplementor factory) {
|
||||
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
|
||||
final QuerySpec subQuery = createIdSubQuery(
|
||||
idSelectCte,
|
||||
fkModelPart,
|
||||
factory
|
||||
);
|
||||
final Expression lhs;
|
||||
if ( lhsExpressions.size() == 1 ) {
|
||||
lhs = lhsExpressions.get( 0 );
|
||||
}
|
||||
else {
|
||||
lhs = new SqlTuple( lhsExpressions, null );
|
||||
}
|
||||
predicate.add(
|
||||
new InSubQueryPredicate(
|
||||
lhs,
|
||||
subQuery,
|
||||
false
|
||||
)
|
||||
);
|
||||
return predicate;
|
||||
}
|
||||
|
||||
protected QuerySpec createIdSubQuery(
|
||||
CteStatement idSelectCte,
|
||||
ModelPart fkModelPart,
|
||||
SessionFactoryImplementor factory) {
|
||||
final NamedTableReference idSelectTableReference = new NamedTableReference(
|
||||
idSelectCte.getCteTable().getTableExpression(),
|
||||
CTE_TABLE_IDENTIFIER,
|
||||
false,
|
||||
factory
|
||||
);
|
||||
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
|
||||
final List<CteColumn> cteColumns = idSelectCte.getCteTable().getCteColumns();
|
||||
final int size = lhsExpressions.size();
|
||||
final int size = cteColumns.size();
|
||||
final QuerySpec subQuery = new QuerySpec( false, 1 );
|
||||
subQuery.getFromClause().addRoot( new CteTableGroup( idSelectTableReference ) );
|
||||
final SelectClause subQuerySelectClause = subQuery.getSelectClause();
|
||||
|
@ -289,28 +313,14 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
|
|||
}
|
||||
);
|
||||
}
|
||||
final Expression lhs;
|
||||
if ( lhsExpressions.size() == 1 ) {
|
||||
lhs = lhsExpressions.get( 0 );
|
||||
}
|
||||
else {
|
||||
lhs = new SqlTuple( lhsExpressions, null );
|
||||
}
|
||||
predicate.add(
|
||||
new InSubQueryPredicate(
|
||||
lhs,
|
||||
subQuery,
|
||||
false
|
||||
)
|
||||
);
|
||||
return predicate;
|
||||
return subQuery;
|
||||
}
|
||||
|
||||
protected abstract void addDmlCtes(
|
||||
CteContainer statement,
|
||||
CteStatement idSelectCte,
|
||||
MultiTableSqmMutationConverter sqmConverter,
|
||||
Map<SqmParameter, List<JdbcParameter>> parameterResolutions,
|
||||
Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
|
||||
SessionFactoryImplementor factory);
|
||||
|
||||
|
||||
|
@ -330,15 +340,5 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
|
|||
}
|
||||
}
|
||||
|
||||
protected String getCteTableName(String tableExpression) {
|
||||
if ( Identifier.isQuoted( tableExpression ) ) {
|
||||
tableExpression = unquote( tableExpression );
|
||||
return DML_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
return DML_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
|
||||
private String unquote(String tableExpression) {
|
||||
return tableExpression.substring( 1, tableExpression.length() - 1 );
|
||||
}
|
||||
protected abstract String getCteTableName(String tableExpression);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
|
@ -40,6 +41,8 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
|
|||
*/
|
||||
public class CteDeleteHandler extends AbstractCteMutationHandler implements DeleteHandler {
|
||||
|
||||
private static final String DELETE_RESULT_TABLE_NAME_PREFIX = "delete_cte_";
|
||||
|
||||
protected CteDeleteHandler(
|
||||
SqmCteTable cteTable,
|
||||
SqmDeleteStatement<?> sqmDeleteStatement,
|
||||
|
@ -54,7 +57,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
CteContainer statement,
|
||||
CteStatement idSelectCte,
|
||||
MultiTableSqmMutationConverter sqmConverter,
|
||||
Map<SqmParameter, List<JdbcParameter>> parameterResolutions,
|
||||
Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
|
||||
SessionFactoryImplementor factory) {
|
||||
final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup();
|
||||
final SelectStatement idSelectStatement = (SelectStatement) idSelectCte.getCteDefinition();
|
||||
|
@ -177,10 +180,19 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCteTableName(String tableExpression) {
|
||||
if ( Identifier.isQuoted( tableExpression ) ) {
|
||||
tableExpression = tableExpression.substring( 1, tableExpression.length() - 1 );
|
||||
return DELETE_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
return DELETE_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
|
||||
protected String getCteTableName(PluralAttributeMapping pluralAttribute) {
|
||||
final String hibernateEntityName = pluralAttribute.findContainingEntityMapping().getEntityName();
|
||||
final String jpaEntityName = getSessionFactory().getJpaMetamodel().entity( hibernateEntityName ).getName();
|
||||
return DML_RESULT_TABLE_NAME_PREFIX + jpaEntityName + "_" + pluralAttribute.getRootPathName().substring(
|
||||
return DELETE_RESULT_TABLE_NAME_PREFIX + jpaEntityName + "_" + pluralAttribute.getRootPathName().substring(
|
||||
hibernateEntityName.length() + 1
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,18 +7,20 @@
|
|||
package org.hibernate.query.sqm.mutation.internal.cte;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.results.TableGroupImpl;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||
import org.hibernate.query.sqm.mutation.internal.UpdateHandler;
|
||||
|
@ -26,18 +28,28 @@ import org.hibernate.query.sqm.tree.cte.SqmCteTable;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.update.SqmSetClause;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.MutationStatement;
|
||||
import org.hibernate.sql.ast.tree.cte.CteContainer;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
import org.hibernate.sql.ast.tree.cte.CteTable;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertStatement;
|
||||
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.ExistsPredicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -45,9 +57,12 @@ import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
|||
*/
|
||||
public class CteUpdateHandler extends AbstractCteMutationHandler implements UpdateHandler {
|
||||
|
||||
private static final String UPDATE_RESULT_TABLE_NAME_PREFIX = "update_cte_";
|
||||
private static final String INSERT_RESULT_TABLE_NAME_PREFIX = "insert_cte_";
|
||||
|
||||
public CteUpdateHandler(
|
||||
SqmCteTable cteTable,
|
||||
SqmUpdateStatement sqmStatement,
|
||||
SqmUpdateStatement<?> sqmStatement,
|
||||
DomainParameterXref domainParameterXref,
|
||||
CteMutationStrategy strategy,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
|
@ -59,13 +74,13 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
|
|||
CteContainer statement,
|
||||
CteStatement idSelectCte,
|
||||
MultiTableSqmMutationConverter sqmConverter,
|
||||
Map<SqmParameter, List<JdbcParameter>> parameterResolutions,
|
||||
Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
|
||||
SessionFactoryImplementor factory) {
|
||||
final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup();
|
||||
final SqmUpdateStatement<?> updateStatement = (SqmUpdateStatement<?>) getSqmDeleteOrUpdateStatement();
|
||||
final EntityMappingType entityDescriptor = getEntityDescriptor();
|
||||
|
||||
final EntityPersister entityPersister = entityDescriptor.getEntityPersister();
|
||||
final AbstractEntityPersister entityPersister = (AbstractEntityPersister) entityDescriptor.getEntityPersister();
|
||||
final String rootEntityName = entityPersister.getRootEntityName();
|
||||
final EntityPersister rootEntityDescriptor = factory.getRuntimeMetamodels()
|
||||
.getMappingMetamodel()
|
||||
|
@ -83,14 +98,12 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
|
|||
// information about the assignments
|
||||
final SqmSetClause setClause = updateStatement.getSetClause();
|
||||
final List<Assignment> assignments = new ArrayList<>( setClause.getAssignments().size() );
|
||||
final Map<SqmParameter, MappingModelExpressible> paramTypeResolutions = new LinkedHashMap<>();
|
||||
|
||||
sqmConverter.visitSetClause(
|
||||
setClause,
|
||||
assignments::add,
|
||||
(sqmParam, mappingType, jdbcParameters) -> {
|
||||
parameterResolutions.put( sqmParam, jdbcParameters );
|
||||
paramTypeResolutions.put( sqmParam, mappingType );
|
||||
}
|
||||
);
|
||||
sqmConverter.addVersionedAssignment( assignments::add, updateStatement );
|
||||
|
@ -139,6 +152,111 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
|
|||
assignmentsForTable.add( assignment );
|
||||
}
|
||||
|
||||
// For nullable tables we have to also generate an insert CTE
|
||||
for (int i = 0; i < entityPersister.getTableSpan(); i++) {
|
||||
if ( entityPersister.isNullableTable( i ) ) {
|
||||
final String tableExpression = entityPersister.getTableName( i );
|
||||
final TableReference updatingTableReference = updatingTableGroup.getTableReference(
|
||||
updatingTableGroup.getNavigablePath(),
|
||||
tableExpression,
|
||||
true,
|
||||
true
|
||||
);
|
||||
final List<Assignment> assignmentList = assignmentsByTable.get( updatingTableReference );
|
||||
if ( assignmentList == null ) {
|
||||
continue;
|
||||
}
|
||||
final CteTable dmlResultCte = new CteTable(
|
||||
getInsertCteTableName( tableExpression ),
|
||||
idSelectCte.getCteTable().getCteColumns(),
|
||||
factory
|
||||
);
|
||||
final NamedTableReference dmlTableReference = resolveUnionTableReference(
|
||||
updatingTableReference,
|
||||
tableExpression
|
||||
);
|
||||
final NamedTableReference existsTableReference = new NamedTableReference(
|
||||
tableExpression,
|
||||
"dml_",
|
||||
false,
|
||||
factory
|
||||
);
|
||||
final List<ColumnReference> existsKeyColumns = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() );
|
||||
final String[] keyColumns = entityPersister.getKeyColumns( i );
|
||||
entityPersister.getIdentifierMapping().forEachSelectable(
|
||||
(selectionIndex, selectableMapping) -> {
|
||||
existsKeyColumns.add(
|
||||
new ColumnReference(
|
||||
existsTableReference,
|
||||
keyColumns[selectionIndex],
|
||||
selectableMapping.getJdbcMapping(),
|
||||
factory
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Copy the subquery contents into a root query
|
||||
final QuerySpec querySpec = createIdSubQuery( idSelectCte, null, factory ).asRootQuery();
|
||||
|
||||
// Prepare a not exists sub-query to avoid violating constraints
|
||||
final QuerySpec existsQuerySpec = new QuerySpec( false );
|
||||
existsQuerySpec.getSelectClause().addSqlSelection(
|
||||
new SqlSelectionImpl(
|
||||
-1,
|
||||
0,
|
||||
new QueryLiteral<>(
|
||||
1,
|
||||
factory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class )
|
||||
)
|
||||
)
|
||||
);
|
||||
existsQuerySpec.getFromClause().addRoot(
|
||||
new TableGroupImpl(
|
||||
null,
|
||||
null,
|
||||
existsTableReference,
|
||||
entityPersister
|
||||
)
|
||||
);
|
||||
|
||||
existsQuerySpec.applyPredicate(
|
||||
new ComparisonPredicate(
|
||||
asExpression( existsKeyColumns ),
|
||||
ComparisonOperator.EQUAL,
|
||||
asExpression( querySpec.getSelectClause() )
|
||||
)
|
||||
);
|
||||
|
||||
querySpec.applyPredicate(
|
||||
new ExistsPredicate(
|
||||
existsQuerySpec,
|
||||
true,
|
||||
factory.getTypeConfiguration().getBasicTypeForJavaType( Boolean.class )
|
||||
)
|
||||
);
|
||||
|
||||
// Collect the target column references from the key expressions
|
||||
final List<ColumnReference> targetColumnReferences = new ArrayList<>( existsKeyColumns );
|
||||
// And transform assignments to target column references and selections
|
||||
for ( Assignment assignment : assignments ) {
|
||||
targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() );
|
||||
querySpec.getSelectClause().addSqlSelection(
|
||||
new SqlSelectionImpl(
|
||||
0,
|
||||
-1,
|
||||
assignment.getAssignedValue()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
final InsertStatement dmlStatement = new InsertStatement( dmlTableReference, existsKeyColumns );
|
||||
dmlStatement.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) );
|
||||
dmlStatement.setSourceSelectStatement( querySpec );
|
||||
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
|
||||
}
|
||||
}
|
||||
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
(tableExpression, tableColumnsVisitationSupplier) -> {
|
||||
final CteTable dmlResultCte = new CteTable(
|
||||
|
@ -203,4 +321,40 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
|
|||
|
||||
throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCteTableName(String tableExpression) {
|
||||
if ( Identifier.isQuoted( tableExpression ) ) {
|
||||
tableExpression = tableExpression.substring( 1, tableExpression.length() - 1 );
|
||||
return UPDATE_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
return UPDATE_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
|
||||
protected String getInsertCteTableName(String tableExpression) {
|
||||
if ( Identifier.isQuoted( tableExpression ) ) {
|
||||
tableExpression = tableExpression.substring( 1, tableExpression.length() - 1 );
|
||||
return INSERT_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
return INSERT_RESULT_TABLE_NAME_PREFIX + tableExpression;
|
||||
}
|
||||
|
||||
private Expression asExpression(SelectClause selectClause) {
|
||||
final List<SqlSelection> sqlSelections = selectClause.getSqlSelections();
|
||||
if ( sqlSelections.size() == 1 ) {
|
||||
return sqlSelections.get( 0 ).getExpression();
|
||||
}
|
||||
final List<Expression> expressions = new ArrayList<>( sqlSelections.size() );
|
||||
for ( SqlSelection sqlSelection : sqlSelections ) {
|
||||
expressions.add( sqlSelection.getExpression() );
|
||||
}
|
||||
return new SqlTuple( expressions, null );
|
||||
}
|
||||
|
||||
private Expression asExpression(List<ColumnReference> columnReferences) {
|
||||
if ( columnReferences.size() == 1 ) {
|
||||
return columnReferences.get( 0 );
|
||||
}
|
||||
return new SqlTuple( columnReferences, null );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.query.sqm.mutation.internal.temptable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -22,28 +23,41 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
|
|||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.results.TableGroupImpl;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.from.UnionTableReference;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertStatement;
|
||||
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.ExistsPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.exec.spi.JdbcInsert;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcUpdate;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -179,6 +193,7 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
(tableExpression, tableKeyColumnVisitationSupplier) -> updateTable(
|
||||
tableExpression,
|
||||
tableKeyColumnVisitationSupplier,
|
||||
rows,
|
||||
idTableSubQuery,
|
||||
executionContext
|
||||
)
|
||||
|
@ -224,6 +239,7 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
private void updateTable(
|
||||
String tableExpression,
|
||||
Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier,
|
||||
int expectedUpdateCount,
|
||||
QuerySpec idTableSubQuery,
|
||||
ExecutionContext executionContext) {
|
||||
final TableReference updatingTableReference = updatingTableGroup.getTableReference(
|
||||
|
@ -259,8 +275,9 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
}
|
||||
);
|
||||
|
||||
final Expression keyExpression = keyColumnCollector.buildKeyExpression();
|
||||
final InSubQueryPredicate idTableSubQueryPredicate = new InSubQueryPredicate(
|
||||
keyColumnCollector.buildKeyExpression(),
|
||||
keyExpression,
|
||||
idTableSubQuery,
|
||||
false
|
||||
);
|
||||
|
@ -268,8 +285,9 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Create the SQL AST and convert it into a JdbcOperation
|
||||
final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression );
|
||||
final UpdateStatement sqlAst = new UpdateStatement(
|
||||
resolveUnionTableReference( updatingTableReference, tableExpression ),
|
||||
dmlTableReference,
|
||||
assignments,
|
||||
idTableSubQueryPredicate
|
||||
);
|
||||
|
@ -280,15 +298,154 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
.buildUpdateTranslator( sessionFactory, sqlAst )
|
||||
.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||
|
||||
jdbcServices.getJdbcMutationExecutor().execute(
|
||||
final int updateCount = jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcUpdate,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {},
|
||||
(integer, preparedStatement) -> {
|
||||
},
|
||||
executionContext
|
||||
);
|
||||
|
||||
if ( updateCount == expectedUpdateCount ) {
|
||||
// We are done when the update count matches
|
||||
return;
|
||||
}
|
||||
// Otherwise we have to check if the table is nullable, and if so, insert into that table
|
||||
final AbstractEntityPersister entityPersister = (AbstractEntityPersister) entityDescriptor.getEntityPersister();
|
||||
boolean isNullable = false;
|
||||
for (int i = 0; i < entityPersister.getTableSpan(); i++) {
|
||||
if ( tableExpression.equals( entityPersister.getTableName( i ) ) && entityPersister.isNullableTable( i ) ) {
|
||||
isNullable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( isNullable ) {
|
||||
// Copy the subquery contents into a root query
|
||||
final QuerySpec querySpec = new QuerySpec( true );
|
||||
for ( TableGroup root : idTableSubQuery.getFromClause().getRoots() ) {
|
||||
querySpec.getFromClause().addRoot( root );
|
||||
}
|
||||
for ( SqlSelection sqlSelection : idTableSubQuery.getSelectClause().getSqlSelections() ) {
|
||||
querySpec.getSelectClause().addSqlSelection( sqlSelection );
|
||||
}
|
||||
querySpec.applyPredicate( idTableSubQuery.getWhereClauseRestrictions() );
|
||||
|
||||
// Prepare a not exists sub-query to avoid violating constraints
|
||||
final QuerySpec existsQuerySpec = new QuerySpec( false );
|
||||
existsQuerySpec.getSelectClause().addSqlSelection(
|
||||
new SqlSelectionImpl(
|
||||
-1,
|
||||
0,
|
||||
new QueryLiteral<>(
|
||||
1,
|
||||
sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class )
|
||||
)
|
||||
)
|
||||
);
|
||||
final NamedTableReference existsTableReference = new NamedTableReference(
|
||||
tableExpression,
|
||||
"dml_",
|
||||
false,
|
||||
sessionFactory
|
||||
);
|
||||
existsQuerySpec.getFromClause().addRoot(
|
||||
new TableGroupImpl(
|
||||
null,
|
||||
null,
|
||||
existsTableReference,
|
||||
entityPersister
|
||||
)
|
||||
);
|
||||
|
||||
final TableKeyExpressionCollector existsKeyColumnCollector = new TableKeyExpressionCollector( entityDescriptor );
|
||||
tableKeyColumnVisitationSupplier.get().accept(
|
||||
(columnIndex, selection) -> {
|
||||
assert selection.getContainingTableExpression().equals( tableExpression );
|
||||
existsKeyColumnCollector.apply(
|
||||
new ColumnReference(
|
||||
existsTableReference,
|
||||
selection,
|
||||
sessionFactory
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
existsQuerySpec.applyPredicate(
|
||||
new ComparisonPredicate(
|
||||
existsKeyColumnCollector.buildKeyExpression(),
|
||||
ComparisonOperator.EQUAL,
|
||||
asExpression(idTableSubQuery.getSelectClause())
|
||||
)
|
||||
);
|
||||
|
||||
querySpec.applyPredicate(
|
||||
new ExistsPredicate(
|
||||
existsQuerySpec,
|
||||
true,
|
||||
sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Boolean.class )
|
||||
)
|
||||
);
|
||||
|
||||
// Collect the target column references from the key expressions
|
||||
final List<ColumnReference> targetColumnReferences = new ArrayList<>();
|
||||
if ( keyExpression instanceof SqlTuple ) {
|
||||
//noinspection unchecked
|
||||
targetColumnReferences.addAll( (Collection<? extends ColumnReference>) ( (SqlTuple) keyExpression ).getExpressions() );
|
||||
}
|
||||
else {
|
||||
targetColumnReferences.add( (ColumnReference) keyExpression );
|
||||
}
|
||||
// And transform assignments to target column references and selections
|
||||
for ( Assignment assignment : assignments ) {
|
||||
targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() );
|
||||
querySpec.getSelectClause().addSqlSelection(
|
||||
new SqlSelectionImpl(
|
||||
0,
|
||||
-1,
|
||||
assignment.getAssignedValue()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
final InsertStatement insertSqlAst = new InsertStatement(
|
||||
dmlTableReference
|
||||
);
|
||||
insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) );
|
||||
insertSqlAst.setSourceSelectStatement( querySpec );
|
||||
|
||||
final JdbcInsert jdbcInsert = jdbcServices.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory()
|
||||
.buildInsertTranslator( sessionFactory, insertSqlAst )
|
||||
.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||
|
||||
final int insertCount = jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcInsert,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {
|
||||
},
|
||||
executionContext
|
||||
);
|
||||
assert insertCount + updateCount == expectedUpdateCount;
|
||||
}
|
||||
}
|
||||
|
||||
private Expression asExpression(SelectClause selectClause) {
|
||||
final List<SqlSelection> sqlSelections = selectClause.getSqlSelections();
|
||||
if ( sqlSelections.size() == 1 ) {
|
||||
return sqlSelections.get( 0 ).getExpression();
|
||||
}
|
||||
final List<Expression> expressions = new ArrayList<>( sqlSelections.size() );
|
||||
for ( SqlSelection sqlSelection : sqlSelections ) {
|
||||
expressions.add( sqlSelection.getExpression() );
|
||||
}
|
||||
return new SqlTuple( expressions, null );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,8 +54,8 @@ public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContain
|
|||
this.selectClause = new SelectClause();
|
||||
}
|
||||
|
||||
private QuerySpec(QuerySpec original) {
|
||||
super( false, original );
|
||||
private QuerySpec(QuerySpec original, boolean root) {
|
||||
super( root, original );
|
||||
this.fromClause = original.fromClause;
|
||||
this.selectClause = original.selectClause;
|
||||
this.whereClauseRestrictions = original.whereClauseRestrictions;
|
||||
|
@ -64,7 +64,11 @@ public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContain
|
|||
}
|
||||
|
||||
public QuerySpec asSubQuery() {
|
||||
return isRoot() ? new QuerySpec( this ) : this;
|
||||
return isRoot() ? new QuerySpec( this, false ) : this;
|
||||
}
|
||||
|
||||
public QuerySpec asRootQuery() {
|
||||
return isRoot() ? this : new QuerySpec( this, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,18 +7,20 @@
|
|||
package org.hibernate.orm.test.mapping;
|
||||
|
||||
import java.util.Date;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.SecondaryTable;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.SecondaryTable;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -36,6 +38,20 @@ public class SecondaryTableTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateOnSecondaryTableColumn(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
session.persist( new SimpleEntityWithSecondaryTables( 1, "test", null, null ) );
|
||||
session.flush();
|
||||
session.clear();
|
||||
session.createQuery( "update SimpleEntityWithSecondaryTables e set e.data = 'test'" ).executeUpdate();
|
||||
SimpleEntityWithSecondaryTables entity = session.get( SimpleEntityWithSecondaryTables.class, 1 );
|
||||
Assertions.assertEquals( "test", entity.data );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity( name = "SimpleEntityWithSecondaryTables" )
|
||||
@Table( name = "simple_w_secondary_tables0" )
|
||||
@SecondaryTable( name = "simple_w_secondary_tables1" )
|
||||
|
|
Loading…
Reference in New Issue