HHH-13715 - working support for "multi-table" HQL/Criteria UPDATE and DELETE queries;
fixed problem with local temp table support - it works!
This commit is contained in:
parent
2ca1bf876b
commit
a654c95c8d
|
@ -221,6 +221,7 @@ abstract class AbstractTransactSQLDialect extends Dialect {
|
|||
return new LocalTemporaryTableStrategy(
|
||||
new IdTable( entityDescriptor, basename -> "#" + basename ),
|
||||
// // sql-server, at least needed this dropped after use; strange!
|
||||
this::getTypeName,
|
||||
AfterUseAction.DROP,
|
||||
TempTableDdlTransactionHandling.NONE
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.sql.DatabaseMetaData;
|
|||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.boot.TempTableDdlTransactionHandling;
|
||||
|
@ -605,7 +606,7 @@ public class DerbyDialect extends DB2Dialect {
|
|||
RuntimeModelCreationContext runtimeModelCreationContext) {
|
||||
return new LocalTemporaryTableStrategy(
|
||||
new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ),
|
||||
() -> new TempIdTableExporter() {
|
||||
() -> new TempIdTableExporter( true, this::getTypeName ) {
|
||||
@Override
|
||||
protected String getCreateCommand() {
|
||||
return "declare global temporary table";
|
||||
|
|
|
@ -377,7 +377,8 @@ public class H2Dialect extends Dialect {
|
|||
RuntimeModelCreationContext runtimeModelCreationContext) {
|
||||
return new LocalTemporaryTableStrategy(
|
||||
new IdTable( entityDescriptor, basename -> "HT_" + basename ),
|
||||
AfterUseAction.NONE,
|
||||
this::getTypeName,
|
||||
AfterUseAction.CLEAN,
|
||||
TempTableDdlTransactionHandling.NONE
|
||||
);
|
||||
}
|
||||
|
|
|
@ -289,7 +289,7 @@ public class InformixDialect extends Dialect {
|
|||
RuntimeModelCreationContext runtimeModelCreationContext) {
|
||||
return new LocalTemporaryTableStrategy(
|
||||
new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ),
|
||||
() -> new TempIdTableExporter() {
|
||||
() -> new TempIdTableExporter( true, this::getTypeName ) {
|
||||
@Override
|
||||
protected String getCreateCommand() {
|
||||
return "create temp table";
|
||||
|
|
|
@ -354,7 +354,7 @@ public class MySQLDialect extends Dialect {
|
|||
|
||||
return new LocalTemporaryTableStrategy(
|
||||
new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ),
|
||||
() -> new TempIdTableExporter() {
|
||||
() -> new TempIdTableExporter( true, this::getTypeName ) {
|
||||
@Override
|
||||
protected String getCreateCommand() {
|
||||
return "create temporary table if not exists";
|
||||
|
|
|
@ -49,12 +49,16 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
|||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
import org.hibernate.type.UUIDCharType;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Support for {@link Handler} implementations
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractTableBasedHandler extends AbstractMutationHandler {
|
||||
private static final Logger log = Logger.getLogger( AbstractTableBasedHandler.class );
|
||||
|
||||
private final IdTable idTable;
|
||||
private final TempTableDdlTransactionHandling ddlTransactionHandling;
|
||||
private final BeforeUseAction beforeUseAction;
|
||||
|
@ -151,6 +155,7 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
try {
|
||||
// 1) save the matching ids into the id table
|
||||
final int affectedRowCount = saveMatchingIdsIntoIdTable( executionContext );
|
||||
log.debugf( "insert for matching ids resulted in %s rows", affectedRowCount );
|
||||
|
||||
// 2) perform the actual individual update or deletes, using
|
||||
// inclusion in the id-table as restriction
|
||||
|
@ -230,6 +235,12 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
|
|||
insertSelectStatement.setTargetTable( idTableReference );
|
||||
insertSelectStatement.setSourceSelectStatement( sqmIdSelectTranslation.getSqlAst() );
|
||||
|
||||
for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) {
|
||||
final IdTableColumn column = idTable.getIdTableColumns().get( i );
|
||||
insertSelectStatement.addTargetColumnReferences(
|
||||
new ColumnReference( idTableReference, column.getColumnName(), column.getJdbcMapping(), factory )
|
||||
);
|
||||
}
|
||||
|
||||
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal.idtable;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.NotYetImplementedFor6Exception;
|
||||
|
@ -44,11 +45,12 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
|
|||
|
||||
public LocalTemporaryTableStrategy(
|
||||
IdTable idTable,
|
||||
Function<Integer, String> databaseTypeNameResolver,
|
||||
AfterUseAction afterUseAction,
|
||||
TempTableDdlTransactionHandling ddlTransactionHandling) {
|
||||
this(
|
||||
idTable,
|
||||
() -> new TempIdTableExporter( true ),
|
||||
() -> new TempIdTableExporter( true, databaseTypeNameResolver ),
|
||||
afterUseAction,
|
||||
ddlTransactionHandling
|
||||
);
|
||||
|
@ -87,7 +89,7 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
|
|||
idTable,
|
||||
idTableExporterAccess,
|
||||
BeforeUseAction.CREATE,
|
||||
AfterUseAction.NONE,
|
||||
afterUseAction,
|
||||
ddlTransactionHandling,
|
||||
domainParameterXref,
|
||||
creationContext
|
||||
|
|
|
@ -34,6 +34,8 @@ import org.hibernate.sql.exec.spi.ExecutionContext;
|
|||
import org.hibernate.sql.exec.spi.JdbcDelete;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -41,6 +43,9 @@ public class TableBasedDeleteHandler
|
|||
extends AbstractTableBasedHandler
|
||||
implements DeleteHandler {
|
||||
|
||||
private static final Logger log = Logger.getLogger( TableBasedDeleteHandler.class );
|
||||
|
||||
|
||||
public TableBasedDeleteHandler(
|
||||
SqmDeleteStatement sqmDeleteStatement,
|
||||
IdTable idTable,
|
||||
|
@ -70,6 +75,8 @@ public class TableBasedDeleteHandler
|
|||
|
||||
@Override
|
||||
protected void performMutations(ExecutionContext executionContext) {
|
||||
log.trace( "performMutations - " + getEntityDescriptor().getEntityName() );
|
||||
|
||||
// create the selection of "matching ids" from the id-table. this is used as the subquery in
|
||||
// used to restrict the deletions from each table
|
||||
final QuerySpec idTableSelectSubQuerySpec = createIdTableSubQuery( executionContext );
|
||||
|
@ -119,6 +126,8 @@ public class TableBasedDeleteHandler
|
|||
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
|
||||
QuerySpec idTableSelectSubQuery,
|
||||
ExecutionContext executionContext) {
|
||||
log.trace( "deleteFrom - " + tableExpression );
|
||||
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
|
||||
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() );
|
||||
|
@ -155,15 +164,18 @@ public class TableBasedDeleteHandler
|
|||
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory );
|
||||
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement );
|
||||
|
||||
jdbcServices.getJdbcDeleteExecutor().execute(
|
||||
final int rows = jdbcServices.getJdbcDeleteExecutor().execute(
|
||||
jdbcDelete,
|
||||
JdbcParameterBindings.NO_BINDINGS,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {},
|
||||
(integer, preparedStatement) -> {
|
||||
},
|
||||
executionContext
|
||||
);
|
||||
|
||||
log.debugf( "delete-from `%s` : %s rows", tableExpression, rows );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,13 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|||
@SuppressWarnings("WeakerAccess")
|
||||
public class TempIdTableExporter implements IdTableExporter {
|
||||
private final boolean isLocal;
|
||||
private final Function<Integer, String> databaseTypeNameResolver;
|
||||
|
||||
public TempIdTableExporter() {
|
||||
this( true );
|
||||
}
|
||||
|
||||
public TempIdTableExporter(boolean isLocal) {
|
||||
public TempIdTableExporter(
|
||||
boolean isLocal,
|
||||
Function<Integer, String> databaseTypeNameResolver) {
|
||||
this.isLocal = isLocal;
|
||||
this.databaseTypeNameResolver = databaseTypeNameResolver;
|
||||
}
|
||||
|
||||
protected String getCreateCommand() {
|
||||
|
@ -57,7 +57,12 @@ public class TempIdTableExporter implements IdTableExporter {
|
|||
}
|
||||
|
||||
buffer.append( column.getColumnName() ).append( ' ' );
|
||||
buffer.append( column.getSqlTypeDefinition() );
|
||||
final String databaseTypeName = databaseTypeNameResolver.apply(
|
||||
column.getJdbcMapping().getSqlTypeDescriptor().getSqlType()
|
||||
);
|
||||
|
||||
buffer.append( " " ).append( databaseTypeName ).append( " " );
|
||||
|
||||
// id values cannot be null
|
||||
buffer.append( " not null" );
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.hibernate.NotYetImplementedFor6Exception;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcInsert;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||
|
@ -32,10 +33,29 @@ public class StandardSqlAstInsertSelectTranslator
|
|||
public JdbcInsert translate(InsertSelectStatement sqlAst) {
|
||||
appendSql( "insert into " );
|
||||
appendSql( sqlAst.getTargetTable().getTableExpression() );
|
||||
appendSql( " " );
|
||||
|
||||
// todo (6.0) : for now we do not provide an explicit target columns (VALUES) list - we should...
|
||||
// it works for our limited usage of insert-select
|
||||
appendSql( " (" );
|
||||
boolean firstPass = true;
|
||||
|
||||
final List<ColumnReference> targetColumnReferences = sqlAst.getTargetColumnReferences();
|
||||
if ( targetColumnReferences == null ) {
|
||||
renderImplicitTargetColumnSpec();
|
||||
}
|
||||
else {
|
||||
for ( int i = 0; i < targetColumnReferences.size(); i++ ) {
|
||||
if ( firstPass ) {
|
||||
firstPass = false;
|
||||
}
|
||||
else {
|
||||
appendSql( ", " );
|
||||
}
|
||||
|
||||
final ColumnReference columnReference = targetColumnReferences.get( i );
|
||||
appendSql( columnReference.getColumnExpression() );
|
||||
}
|
||||
}
|
||||
|
||||
appendSql( ") " );
|
||||
|
||||
visitQuerySpec( sqlAst.getSourceSelectStatement() );
|
||||
|
||||
|
@ -57,6 +77,9 @@ public class StandardSqlAstInsertSelectTranslator
|
|||
};
|
||||
}
|
||||
|
||||
private void renderImplicitTargetColumnSpec() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcOperation translate(CteStatement cteStatement) {
|
||||
throw new NotYetImplementedFor6Exception( getClass() );
|
||||
|
|
|
@ -132,7 +132,6 @@ public class HqlDeleteExecutionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected( reason = "No idea why this one fails. H2 complains inserting the temp table that one of the PK values we select is NULL" )
|
||||
public void testJoinedSubclassRootRestrictedDeleteResults(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -158,6 +157,13 @@ public class HqlDeleteExecutionTests {
|
|||
assertThat( rows, is( 1 ) );
|
||||
}
|
||||
);
|
||||
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
final int rows = session.createQuery( "delete from Customer" ).executeUpdate();
|
||||
assertThat( rows, is( 0 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -182,7 +188,6 @@ public class HqlDeleteExecutionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailureExpected( reason = "No idea why this one fails. H2 complains inserting the temp table that one of the PK values we select is NULL" )
|
||||
public void testJoinedSubclassLeafRestrictedDeleteResult(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -196,11 +201,27 @@ public class HqlDeleteExecutionTests {
|
|||
);
|
||||
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "delete ForeignCustomer where name = 'Adventures Abroad'" ).executeUpdate()
|
||||
session -> {
|
||||
final int rows = session.createQuery( "delete ForeignCustomer where name = 'Adventures Abroad'" )
|
||||
.executeUpdate();
|
||||
assertThat( rows, is( 1 ) );
|
||||
}
|
||||
);
|
||||
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "delete DomesticCustomer where name = 'Domestic Wonders'" ).executeUpdate()
|
||||
session -> {
|
||||
final int rows = session.createQuery( "delete DomesticCustomer where name = 'Domestic Wonders'" )
|
||||
.executeUpdate();
|
||||
assertThat( rows, is( 1 ) );
|
||||
}
|
||||
);
|
||||
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
final int rows = session.createQuery( "delete Customer" )
|
||||
.executeUpdate();
|
||||
assertThat( rows, is( 0 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ log4j.logger.org.hibernate.orm.graph=debug
|
|||
log4j.logger.org.hibernate.orm.query.sqm=debug
|
||||
log4j.logger.org.hibernate.orm.query.hql=debug
|
||||
|
||||
log4j.logger.org.hibernate.query.sqm.mutation.internal.idtable.TableBasedDeleteHandler=trace
|
||||
|
||||
log4j.logger.org.hibernate.tool.hbm2ddl=trace
|
||||
log4j.logger.org.hibernate.testing.cache=debug
|
||||
|
|
Loading…
Reference in New Issue