Make sure DML updates do inserts into secondary tables when necessary

This commit is contained in:
Christian Beikov 2022-03-10 16:24:22 +01:00
parent 955e8265dc
commit 20564a5547
6 changed files with 397 additions and 54 deletions

View File

@ -15,7 +15,6 @@ import java.util.Map;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
@ -69,7 +68,6 @@ import org.hibernate.type.spi.TypeConfiguration;
*/ */
public abstract class AbstractCteMutationHandler extends AbstractMutationHandler { 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"; public static final String CTE_TABLE_IDENTIFIER = "id";
private final SqmCteTable cteTable; private final SqmCteTable cteTable;
@ -126,7 +124,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
executionContext.getQueryParameterBindings(), executionContext.getQueryParameterBindings(),
factory factory
); );
final Map<SqmParameter, List<JdbcParameter>> parameterResolutions; final Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions;
if ( domainParameterXref.getSqmParameterCount() == 0 ) { if ( domainParameterXref.getSqmParameterCount() == 0 ) {
parameterResolutions = Collections.emptyMap(); parameterResolutions = Collections.emptyMap();
} }
@ -148,7 +146,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
MatchingIdSelectionHelper.generateMatchingIdSelectStatement( MatchingIdSelectionHelper.generateMatchingIdSelectStatement(
entityDescriptor, entityDescriptor,
sqmMutationStatement, sqmMutationStatement,
false, true,
restriction, restriction,
sqmConverter, sqmConverter,
executionContext, executionContext,
@ -242,15 +240,41 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
CteStatement idSelectCte, CteStatement idSelectCte,
ModelPart fkModelPart, ModelPart fkModelPart,
SessionFactoryImplementor factory) { 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( final NamedTableReference idSelectTableReference = new NamedTableReference(
idSelectCte.getCteTable().getTableExpression(), idSelectCte.getCteTable().getTableExpression(),
CTE_TABLE_IDENTIFIER, CTE_TABLE_IDENTIFIER,
false, false,
factory factory
); );
final Junction predicate = new Junction( Junction.Nature.CONJUNCTION );
final List<CteColumn> cteColumns = idSelectCte.getCteTable().getCteColumns(); final List<CteColumn> cteColumns = idSelectCte.getCteTable().getCteColumns();
final int size = lhsExpressions.size(); final int size = cteColumns.size();
final QuerySpec subQuery = new QuerySpec( false, 1 ); final QuerySpec subQuery = new QuerySpec( false, 1 );
subQuery.getFromClause().addRoot( new CteTableGroup( idSelectTableReference ) ); subQuery.getFromClause().addRoot( new CteTableGroup( idSelectTableReference ) );
final SelectClause subQuerySelectClause = subQuery.getSelectClause(); final SelectClause subQuerySelectClause = subQuery.getSelectClause();
@ -289,28 +313,14 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
} }
); );
} }
final Expression lhs; return subQuery;
if ( lhsExpressions.size() == 1 ) {
lhs = lhsExpressions.get( 0 );
}
else {
lhs = new SqlTuple( lhsExpressions, null );
}
predicate.add(
new InSubQueryPredicate(
lhs,
subQuery,
false
)
);
return predicate;
} }
protected abstract void addDmlCtes( protected abstract void addDmlCtes(
CteContainer statement, CteContainer statement,
CteStatement idSelectCte, CteStatement idSelectCte,
MultiTableSqmMutationConverter sqmConverter, MultiTableSqmMutationConverter sqmConverter,
Map<SqmParameter, List<JdbcParameter>> parameterResolutions, Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
SessionFactoryImplementor factory); SessionFactoryImplementor factory);
@ -330,15 +340,5 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
} }
} }
protected String getCteTableName(String tableExpression) { protected abstract 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 );
}
} }

View File

@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; 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 { public class CteDeleteHandler extends AbstractCteMutationHandler implements DeleteHandler {
private static final String DELETE_RESULT_TABLE_NAME_PREFIX = "delete_cte_";
protected CteDeleteHandler( protected CteDeleteHandler(
SqmCteTable cteTable, SqmCteTable cteTable,
SqmDeleteStatement<?> sqmDeleteStatement, SqmDeleteStatement<?> sqmDeleteStatement,
@ -54,7 +57,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
CteContainer statement, CteContainer statement,
CteStatement idSelectCte, CteStatement idSelectCte,
MultiTableSqmMutationConverter sqmConverter, MultiTableSqmMutationConverter sqmConverter,
Map<SqmParameter, List<JdbcParameter>> parameterResolutions, Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
SessionFactoryImplementor factory) { SessionFactoryImplementor factory) {
final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup(); final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup();
final SelectStatement idSelectStatement = (SelectStatement) idSelectCte.getCteDefinition(); 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) { protected String getCteTableName(PluralAttributeMapping pluralAttribute) {
final String hibernateEntityName = pluralAttribute.findContainingEntityMapping().getEntityName(); final String hibernateEntityName = pluralAttribute.findContainingEntityMapping().getEntityName();
final String jpaEntityName = getSessionFactory().getJpaMetamodel().entity( hibernateEntityName ).getName(); 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 hibernateEntityName.length() + 1
); );
} }

View File

@ -7,18 +7,20 @@
package org.hibernate.query.sqm.mutation.internal.cte; package org.hibernate.query.sqm.mutation.internal.cte;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.EntityMappingType; 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.EntityPersister;
import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.Joinable;
import org.hibernate.query.SemanticException; 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.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
import org.hibernate.query.sqm.mutation.internal.UpdateHandler; 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.expression.SqmParameter;
import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; 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.MutationStatement;
import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.cte.CteTable; import org.hibernate.sql.ast.tree.cte.CteTable;
import org.hibernate.sql.ast.tree.expression.ColumnReference; 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.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.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin; 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.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement; 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 { 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( public CteUpdateHandler(
SqmCteTable cteTable, SqmCteTable cteTable,
SqmUpdateStatement sqmStatement, SqmUpdateStatement<?> sqmStatement,
DomainParameterXref domainParameterXref, DomainParameterXref domainParameterXref,
CteMutationStrategy strategy, CteMutationStrategy strategy,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
@ -59,13 +74,13 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
CteContainer statement, CteContainer statement,
CteStatement idSelectCte, CteStatement idSelectCte,
MultiTableSqmMutationConverter sqmConverter, MultiTableSqmMutationConverter sqmConverter,
Map<SqmParameter, List<JdbcParameter>> parameterResolutions, Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
SessionFactoryImplementor factory) { SessionFactoryImplementor factory) {
final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup(); final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup();
final SqmUpdateStatement<?> updateStatement = (SqmUpdateStatement<?>) getSqmDeleteOrUpdateStatement(); final SqmUpdateStatement<?> updateStatement = (SqmUpdateStatement<?>) getSqmDeleteOrUpdateStatement();
final EntityMappingType entityDescriptor = getEntityDescriptor(); final EntityMappingType entityDescriptor = getEntityDescriptor();
final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); final AbstractEntityPersister entityPersister = (AbstractEntityPersister) entityDescriptor.getEntityPersister();
final String rootEntityName = entityPersister.getRootEntityName(); final String rootEntityName = entityPersister.getRootEntityName();
final EntityPersister rootEntityDescriptor = factory.getRuntimeMetamodels() final EntityPersister rootEntityDescriptor = factory.getRuntimeMetamodels()
.getMappingMetamodel() .getMappingMetamodel()
@ -83,14 +98,12 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
// information about the assignments // information about the assignments
final SqmSetClause setClause = updateStatement.getSetClause(); final SqmSetClause setClause = updateStatement.getSetClause();
final List<Assignment> assignments = new ArrayList<>( setClause.getAssignments().size() ); final List<Assignment> assignments = new ArrayList<>( setClause.getAssignments().size() );
final Map<SqmParameter, MappingModelExpressible> paramTypeResolutions = new LinkedHashMap<>();
sqmConverter.visitSetClause( sqmConverter.visitSetClause(
setClause, setClause,
assignments::add, assignments::add,
(sqmParam, mappingType, jdbcParameters) -> { (sqmParam, mappingType, jdbcParameters) -> {
parameterResolutions.put( sqmParam, jdbcParameters ); parameterResolutions.put( sqmParam, jdbcParameters );
paramTypeResolutions.put( sqmParam, mappingType );
} }
); );
sqmConverter.addVersionedAssignment( assignments::add, updateStatement ); sqmConverter.addVersionedAssignment( assignments::add, updateStatement );
@ -139,6 +152,111 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
assignmentsForTable.add( assignment ); 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( getEntityDescriptor().visitConstraintOrderedTables(
(tableExpression, tableColumnsVisitationSupplier) -> { (tableExpression, tableColumnsVisitationSupplier) -> {
final CteTable dmlResultCte = new CteTable( 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 ); 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 );
}
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.mutation.internal.temptable; package org.hibernate.query.sqm.mutation.internal.temptable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; 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.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.query.SemanticException; import org.hibernate.query.SemanticException;
import org.hibernate.query.results.TableGroupImpl;
import org.hibernate.query.spi.DomainQueryExecutionContext; 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.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; 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.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter; 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.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.UnionTableReference; 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.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec; 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.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.ExecutionContext; 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.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcUpdate; import org.hibernate.sql.exec.spi.JdbcUpdate;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -179,6 +193,7 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
(tableExpression, tableKeyColumnVisitationSupplier) -> updateTable( (tableExpression, tableKeyColumnVisitationSupplier) -> updateTable(
tableExpression, tableExpression,
tableKeyColumnVisitationSupplier, tableKeyColumnVisitationSupplier,
rows,
idTableSubQuery, idTableSubQuery,
executionContext executionContext
) )
@ -224,6 +239,7 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
private void updateTable( private void updateTable(
String tableExpression, String tableExpression,
Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier, Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier,
int expectedUpdateCount,
QuerySpec idTableSubQuery, QuerySpec idTableSubQuery,
ExecutionContext executionContext) { ExecutionContext executionContext) {
final TableReference updatingTableReference = updatingTableGroup.getTableReference( 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( final InSubQueryPredicate idTableSubQueryPredicate = new InSubQueryPredicate(
keyColumnCollector.buildKeyExpression(), keyExpression,
idTableSubQuery, idTableSubQuery,
false false
); );
@ -268,8 +285,9 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create the SQL AST and convert it into a JdbcOperation // Create the SQL AST and convert it into a JdbcOperation
final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression );
final UpdateStatement sqlAst = new UpdateStatement( final UpdateStatement sqlAst = new UpdateStatement(
resolveUnionTableReference( updatingTableReference, tableExpression ), dmlTableReference,
assignments, assignments,
idTableSubQueryPredicate idTableSubQueryPredicate
); );
@ -280,15 +298,154 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
.buildUpdateTranslator( sessionFactory, sqlAst ) .buildUpdateTranslator( sessionFactory, sqlAst )
.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); .translate( jdbcParameterBindings, executionContext.getQueryOptions() );
jdbcServices.getJdbcMutationExecutor().execute( final int updateCount = jdbcServices.getJdbcMutationExecutor().execute(
jdbcUpdate, jdbcUpdate,
jdbcParameterBindings, jdbcParameterBindings,
sql -> executionContext.getSession() sql -> executionContext.getSession()
.getJdbcCoordinator() .getJdbcCoordinator()
.getStatementPreparer() .getStatementPreparer()
.prepareStatement( sql ), .prepareStatement( sql ),
(integer, preparedStatement) -> {}, (integer, preparedStatement) -> {
},
executionContext 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 );
} }
} }

View File

@ -54,8 +54,8 @@ public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContain
this.selectClause = new SelectClause(); this.selectClause = new SelectClause();
} }
private QuerySpec(QuerySpec original) { private QuerySpec(QuerySpec original, boolean root) {
super( false, original ); super( root, original );
this.fromClause = original.fromClause; this.fromClause = original.fromClause;
this.selectClause = original.selectClause; this.selectClause = original.selectClause;
this.whereClauseRestrictions = original.whereClauseRestrictions; this.whereClauseRestrictions = original.whereClauseRestrictions;
@ -64,7 +64,11 @@ public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContain
} }
public QuerySpec asSubQuery() { 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 @Override

View File

@ -7,18 +7,20 @@
package org.hibernate.orm.test.mapping; package org.hibernate.orm.test.mapping;
import java.util.Date; 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.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; 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 * @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" ) @Entity( name = "SimpleEntityWithSecondaryTables" )
@Table( name = "simple_w_secondary_tables0" ) @Table( name = "simple_w_secondary_tables0" )
@SecondaryTable( name = "simple_w_secondary_tables1" ) @SecondaryTable( name = "simple_w_secondary_tables1" )