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( return new LocalTemporaryTableStrategy(
new IdTable( entityDescriptor, basename -> "#" + basename ), new IdTable( entityDescriptor, basename -> "#" + basename ),
// // sql-server, at least needed this dropped after use; strange! // // sql-server, at least needed this dropped after use; strange!
this::getTypeName,
AfterUseAction.DROP, AfterUseAction.DROP,
TempTableDdlTransactionHandling.NONE TempTableDdlTransactionHandling.NONE
); );

View File

@ -11,6 +11,7 @@ import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.Locale; import java.util.Locale;
import java.util.function.Function;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.TempTableDdlTransactionHandling;
@ -605,7 +606,7 @@ public class DerbyDialect extends DB2Dialect {
RuntimeModelCreationContext runtimeModelCreationContext) { RuntimeModelCreationContext runtimeModelCreationContext) {
return new LocalTemporaryTableStrategy( return new LocalTemporaryTableStrategy(
new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ), new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ),
() -> new TempIdTableExporter() { () -> new TempIdTableExporter( true, this::getTypeName ) {
@Override @Override
protected String getCreateCommand() { protected String getCreateCommand() {
return "declare global temporary table"; return "declare global temporary table";

View File

@ -377,7 +377,8 @@ public class H2Dialect extends Dialect {
RuntimeModelCreationContext runtimeModelCreationContext) { RuntimeModelCreationContext runtimeModelCreationContext) {
return new LocalTemporaryTableStrategy( return new LocalTemporaryTableStrategy(
new IdTable( entityDescriptor, basename -> "HT_" + basename ), new IdTable( entityDescriptor, basename -> "HT_" + basename ),
AfterUseAction.NONE, this::getTypeName,
AfterUseAction.CLEAN,
TempTableDdlTransactionHandling.NONE TempTableDdlTransactionHandling.NONE
); );
} }

View File

@ -289,7 +289,7 @@ public class InformixDialect extends Dialect {
RuntimeModelCreationContext runtimeModelCreationContext) { RuntimeModelCreationContext runtimeModelCreationContext) {
return new LocalTemporaryTableStrategy( return new LocalTemporaryTableStrategy(
new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ), new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ),
() -> new TempIdTableExporter() { () -> new TempIdTableExporter( true, this::getTypeName ) {
@Override @Override
protected String getCreateCommand() { protected String getCreateCommand() {
return "create temp table"; return "create temp table";

View File

@ -354,7 +354,7 @@ public class MySQLDialect extends Dialect {
return new LocalTemporaryTableStrategy( return new LocalTemporaryTableStrategy(
new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ), new IdTable( rootEntityDescriptor, basename -> "HT_" + basename ),
() -> new TempIdTableExporter() { () -> new TempIdTableExporter( true, this::getTypeName ) {
@Override @Override
protected String getCreateCommand() { protected String getCreateCommand() {
return "create temporary table if not exists"; 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.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.UUIDCharType; import org.hibernate.type.UUIDCharType;
import org.jboss.logging.Logger;
/** /**
* Support for {@link Handler} implementations * Support for {@link Handler} implementations
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public abstract class AbstractTableBasedHandler extends AbstractMutationHandler { public abstract class AbstractTableBasedHandler extends AbstractMutationHandler {
private static final Logger log = Logger.getLogger( AbstractTableBasedHandler.class );
private final IdTable idTable; private final IdTable idTable;
private final TempTableDdlTransactionHandling ddlTransactionHandling; private final TempTableDdlTransactionHandling ddlTransactionHandling;
private final BeforeUseAction beforeUseAction; private final BeforeUseAction beforeUseAction;
@ -151,6 +155,7 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
try { try {
// 1) save the matching ids into the id table // 1) save the matching ids into the id table
final int affectedRowCount = saveMatchingIdsIntoIdTable( executionContext ); 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 // 2) perform the actual individual update or deletes, using
// inclusion in the id-table as restriction // inclusion in the id-table as restriction
@ -230,6 +235,12 @@ public abstract class AbstractTableBasedHandler extends AbstractMutationHandler
insertSelectStatement.setTargetTable( idTableReference ); insertSelectStatement.setTargetTable( idTableReference );
insertSelectStatement.setSourceSelectStatement( sqmIdSelectTranslation.getSqlAst() ); 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 JdbcServices jdbcServices = factory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.query.sqm.mutation.internal.idtable; package org.hibernate.query.sqm.mutation.internal.idtable;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
@ -44,11 +45,12 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
public LocalTemporaryTableStrategy( public LocalTemporaryTableStrategy(
IdTable idTable, IdTable idTable,
Function<Integer, String> databaseTypeNameResolver,
AfterUseAction afterUseAction, AfterUseAction afterUseAction,
TempTableDdlTransactionHandling ddlTransactionHandling) { TempTableDdlTransactionHandling ddlTransactionHandling) {
this( this(
idTable, idTable,
() -> new TempIdTableExporter( true ), () -> new TempIdTableExporter( true, databaseTypeNameResolver ),
afterUseAction, afterUseAction,
ddlTransactionHandling ddlTransactionHandling
); );
@ -87,7 +89,7 @@ public class LocalTemporaryTableStrategy implements SqmMultiTableMutationStrateg
idTable, idTable,
idTableExporterAccess, idTableExporterAccess,
BeforeUseAction.CREATE, BeforeUseAction.CREATE,
AfterUseAction.NONE, afterUseAction,
ddlTransactionHandling, ddlTransactionHandling,
domainParameterXref, domainParameterXref,
creationContext 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.JdbcDelete;
import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.jboss.logging.Logger;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -41,6 +43,9 @@ public class TableBasedDeleteHandler
extends AbstractTableBasedHandler extends AbstractTableBasedHandler
implements DeleteHandler { implements DeleteHandler {
private static final Logger log = Logger.getLogger( TableBasedDeleteHandler.class );
public TableBasedDeleteHandler( public TableBasedDeleteHandler(
SqmDeleteStatement sqmDeleteStatement, SqmDeleteStatement sqmDeleteStatement,
IdTable idTable, IdTable idTable,
@ -70,6 +75,8 @@ public class TableBasedDeleteHandler
@Override @Override
protected void performMutations(ExecutionContext executionContext) { 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 // 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 // used to restrict the deletions from each table
final QuerySpec idTableSelectSubQuerySpec = createIdTableSubQuery( executionContext ); final QuerySpec idTableSelectSubQuerySpec = createIdTableSubQuery( executionContext );
@ -119,6 +126,8 @@ public class TableBasedDeleteHandler
Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier, Supplier<Consumer<ColumnConsumer>> tableKeyColumnVisitationSupplier,
QuerySpec idTableSelectSubQuery, QuerySpec idTableSelectSubQuery,
ExecutionContext executionContext) { ExecutionContext executionContext) {
log.trace( "deleteFrom - " + tableExpression );
final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() );
@ -155,15 +164,18 @@ public class TableBasedDeleteHandler
final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory ); final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory );
final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement ); final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement );
jdbcServices.getJdbcDeleteExecutor().execute( final int rows = jdbcServices.getJdbcDeleteExecutor().execute(
jdbcDelete, jdbcDelete,
JdbcParameterBindings.NO_BINDINGS, JdbcParameterBindings.NO_BINDINGS,
sql -> executionContext.getSession() sql -> executionContext.getSession()
.getJdbcCoordinator() .getJdbcCoordinator()
.getStatementPreparer() .getStatementPreparer()
.prepareStatement( sql ), .prepareStatement( sql ),
(integer, preparedStatement) -> {}, (integer, preparedStatement) -> {
},
executionContext executionContext
); );
log.debugf( "delete-from `%s` : %s rows", tableExpression, rows );
} }
} }

View File

@ -16,13 +16,13 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class TempIdTableExporter implements IdTableExporter { public class TempIdTableExporter implements IdTableExporter {
private final boolean isLocal; private final boolean isLocal;
private final Function<Integer, String> databaseTypeNameResolver;
public TempIdTableExporter() { public TempIdTableExporter(
this( true ); boolean isLocal,
} Function<Integer, String> databaseTypeNameResolver) {
public TempIdTableExporter(boolean isLocal) {
this.isLocal = isLocal; this.isLocal = isLocal;
this.databaseTypeNameResolver = databaseTypeNameResolver;
} }
protected String getCreateCommand() { protected String getCreateCommand() {
@ -57,7 +57,12 @@ public class TempIdTableExporter implements IdTableExporter {
} }
buffer.append( column.getColumnName() ).append( ' ' ); 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 // id values cannot be null
buffer.append( " not null" ); buffer.append( " not null" );
} }

View File

@ -13,6 +13,7 @@ import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstInsertSelectTranslator; import org.hibernate.sql.ast.SqlAstInsertSelectTranslator;
import org.hibernate.sql.ast.tree.cte.CteStatement; 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.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.exec.spi.JdbcInsert; import org.hibernate.sql.exec.spi.JdbcInsert;
import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperation;
@ -32,10 +33,29 @@ public class StandardSqlAstInsertSelectTranslator
public JdbcInsert translate(InsertSelectStatement sqlAst) { public JdbcInsert translate(InsertSelectStatement sqlAst) {
appendSql( "insert into " ); appendSql( "insert into " );
appendSql( sqlAst.getTargetTable().getTableExpression() ); appendSql( sqlAst.getTargetTable().getTableExpression() );
appendSql( " " );
// todo (6.0) : for now we do not provide an explicit target columns (VALUES) list - we should... appendSql( " (" );
// it works for our limited usage of insert-select 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() ); visitQuerySpec( sqlAst.getSourceSelectStatement() );
@ -57,6 +77,9 @@ public class StandardSqlAstInsertSelectTranslator
}; };
} }
private void renderImplicitTargetColumnSpec() {
}
@Override @Override
public JdbcOperation translate(CteStatement cteStatement) { public JdbcOperation translate(CteStatement cteStatement) {
throw new NotYetImplementedFor6Exception( getClass() ); throw new NotYetImplementedFor6Exception( getClass() );

View File

@ -132,7 +132,6 @@ public class HqlDeleteExecutionTests {
} }
@Test @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) { public void testJoinedSubclassRootRestrictedDeleteResults(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
@ -158,6 +157,13 @@ public class HqlDeleteExecutionTests {
assertThat( rows, is( 1 ) ); 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 @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) { public void testJoinedSubclassLeafRestrictedDeleteResult(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
@ -196,11 +201,27 @@ public class HqlDeleteExecutionTests {
); );
scope.inTransaction( 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( 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.sqm=debug
log4j.logger.org.hibernate.orm.query.hql=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.tool.hbm2ddl=trace
log4j.logger.org.hibernate.testing.cache=debug log4j.logger.org.hibernate.testing.cache=debug