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:
Steve Ebersole 2019-11-12 11:30:41 -06:00
parent 2ca1bf876b
commit a654c95c8d
12 changed files with 99 additions and 21 deletions

View File

@ -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
);

View File

@ -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";

View File

@ -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
);
}

View File

@ -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";

View File

@ -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";

View File

@ -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();

View File

@ -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

View File

@ -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 );
}
}

View File

@ -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" );
}

View File

@ -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() );

View File

@ -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 ) );
}
);
}

View File

@ -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