HHH-11262 - Bulk Operations attempt to create temporary tables, but user does not have permission to create table

This commit is contained in:
Evandro Pires da Silva 2016-12-18 21:04:14 -02:00 committed by Vlad Mihalcea
parent 8f6e4d5675
commit d48f393420
43 changed files with 2348 additions and 14 deletions

View File

@ -2865,6 +2865,24 @@ public abstract class Dialect implements ConversionContext {
return true;
}
/**
* Does this dialect/database support non-query statements (e.g. INSERT, UPDATE, DELETE) with CTE (Common Table Expressions)?
*
* @return {@code true} if non-query statements are supported with CTE
*/
public boolean supportsNonQueryWithCTE() {
return false;
}
/**
* Does this dialect/database support VALUES list (e.g. VALUES (1), (2), (3) )
*
* @return {@code true} if VALUES list are supported
*/
public boolean supportsValuesList() {
return false;
}
public boolean isLegacyLimitHandlerBehaviorEnabled() {
return legacyLimitHandlerBehavior;
}

View File

@ -65,4 +65,12 @@ public class MySQL57InnoDBDialect extends MySQL5InnoDBDialect {
// from_unixtime(), timestamp() are functions that return TIMESTAMP that do not support a
// fractional seconds precision argument (so there's no need to override them here):
}
/**
* @see <a href="https://dev.mysql.com/worklog/task/?id=7019">MySQL 5.7 work log</a>
* @return supports IN clause row value expressions
*/
public boolean supportsRowValueConstructorSyntaxInInList() {
return true;
}
}

View File

@ -56,4 +56,13 @@ public class PostgreSQL82Dialect extends PostgreSQL81Dialect {
public String getDropSequenceString(String sequenceName) {
return "drop sequence if exists " + sequenceName;
}
@Override
public boolean supportsValuesList() {
return true;
}
public boolean supportsRowValueConstructorSyntaxInInList() {
return true;
}
}

View File

@ -17,4 +17,9 @@ public class PostgreSQL91Dialect extends PostgreSQL9Dialect {
public boolean supportsPartitionBy() {
return true;
}
@Override
public boolean supportsNonQueryWithCTE() {
return true;
}
}

View File

@ -105,4 +105,9 @@ public class SQLServer2005Dialect extends SQLServerDialect {
}
};
}
@Override
public boolean supportsNonQueryWithCTE() {
return true;
}
}

View File

@ -56,4 +56,9 @@ public class SQLServer2008Dialect extends SQLServer2005Dialect {
return orderByElement.toString();
}
@Override
public boolean supportsValuesList() {
return true;
}
}

View File

@ -0,0 +1,106 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.JDBCException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.tree.AbstractRestrictableStatement;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.persister.entity.Queryable;
/**
* Base class for all strategies that select the ids to be updated/deleted prior to executing the update/delete operation.
*
* @author Vlad Mihalcea
*/
public abstract class AbstractIdsBulkIdHandler
extends AbstractTableBasedBulkIdHandler {
private final Queryable targetedPersister;
private final String idSelect;
private final List<ParameterSpecification> idSelectParameterSpecifications;
public AbstractIdsBulkIdHandler(
SessionFactoryImplementor sessionFactory, HqlSqlWalker walker) {
super(sessionFactory, walker);
final AbstractRestrictableStatement statement = (AbstractRestrictableStatement) walker.getAST();
final FromElement fromElement = statement.getFromClause().getFromElement();
this.targetedPersister = fromElement.getQueryable();
final ProcessedWhereClause processedWhereClause = processWhereClause( statement.getWhereClause() );
this.idSelectParameterSpecifications = processedWhereClause.getIdSelectParameterSpecifications();
final String bulkTargetAlias = fromElement.getTableAlias();
this.idSelect = generateIdSelect( bulkTargetAlias, processedWhereClause ).toStatementString();
}
@Override
public Queryable getTargetedQueryable() {
return targetedPersister;
}
protected Dialect dialect() {
return factory().getServiceRegistry().getService( JdbcServices.class ).getDialect();
}
protected JDBCException convert(
SQLException e,
String message,
String sql) {
throw factory().getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().convert( e, message, sql );
}
protected List<Object[]> selectIds(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
List<Object[]> ids = new ArrayList<>();
try {
try (PreparedStatement ps = session.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( idSelect, false )) {
int position = 1;
for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) {
position += parameterSpecification.bind( ps, queryParameters, session, position );
}
ResultSet rs = session
.getJdbcCoordinator()
.getResultSetReturn()
.extract( ps );
while ( rs.next() ) {
Object[] result = new Object[targetedPersister.getIdentifierColumnNames().length];
for ( String columnName : targetedPersister.getIdentifierColumnNames() ) {
Object column = rs.getObject( columnName );
result[rs.findColumn( columnName ) - 1] = column;
}
ids.add( result );
}
}
}
catch ( SQLException e ) {
throw convert( e, "could not select ids for bulk operation", idSelect );
}
return ids;
}
}

View File

@ -127,6 +127,22 @@ public abstract class AbstractTableBasedBulkIdHandler {
IdTableInfo idTableInfo,
ProcessedWhereClause whereClause) {
final Dialect dialect = sessionFactory.getJdbcServices().getJdbcEnvironment().getDialect();
final Select select = generateIdSelect( tableAlias, whereClause );
InsertSelect insert = new InsertSelect( dialect );
if ( sessionFactory.getSessionFactoryOptions().isCommentsEnabled() ) {
insert.setComment( "insert-select for " + getTargetedQueryable().getEntityName() + " ids" );
}
insert.setTableName( idTableInfo.getQualifiedIdTableName() );
insert.setSelect( select );
return insert.toStatementString();
}
protected Select generateIdSelect(
String tableAlias,
ProcessedWhereClause whereClause) {
final Dialect dialect = sessionFactory.getJdbcServices().getJdbcEnvironment().getDialect();
final Select select = new Select( dialect );
@ -160,14 +176,7 @@ public abstract class AbstractTableBasedBulkIdHandler {
}
}
select.setWhereClause( whereJoinFragment + whereClause.getUserWhereClauseFragment() );
InsertSelect insert = new InsertSelect( dialect );
if ( sessionFactory.getSessionFactoryOptions().isCommentsEnabled() ) {
insert.setComment( "insert-select for " + getTargetedQueryable().getEntityName() + " ids" );
}
insert.setTableName( idTableInfo.getQualifiedIdTableName() );
insert.setSelect( select );
return insert.toStatementString();
return select;
}
/**

View File

@ -114,10 +114,10 @@ public class TableBasedDeleteHandlerImpl
try {
try {
ps = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( idInsertSelect, false );
int pos = 1;
pos += handlePrependedParametersOnIdSelection( ps, session, pos );
int position = 1;
position += handlePrependedParametersOnIdSelection( ps, session, position );
for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) {
pos += parameterSpecification.bind( ps, queryParameters, session, pos );
position += parameterSpecification.bind( ps, queryParameters, session, position );
}
resultCount = session.getJdbcCoordinator().getResultSetReturn().executeUpdate( ps );
}

View File

@ -116,10 +116,10 @@ public class TableBasedUpdateHandlerImpl
try {
try {
ps = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( idInsertSelect, false );
int sum = 1;
sum += handlePrependedParametersOnIdSelection( ps, session, sum );
int position = 1;
position += handlePrependedParametersOnIdSelection( ps, session, position );
for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) {
sum += parameterSpecification.bind( ps, queryParameters, session, sum );
position += parameterSpecification.bind( ps, queryParameters, session, position );
}
resultCount = session.getJdbcCoordinator().getResultSetReturn().executeUpdate( ps );
}

View File

@ -0,0 +1,98 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.cte;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.AbstractIdsBulkIdHandler;
import org.hibernate.persister.entity.Queryable;
/**
* Defines how identifier values are selected from the updatable/deletable tables.
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
*/
public abstract class AbstractCteValuesListBulkIdHandler extends
AbstractIdsBulkIdHandler {
private final String catalog;
private final String schema;
private final JdbcEnvironment jdbcEnvironment;
public AbstractCteValuesListBulkIdHandler(
SessionFactoryImplementor sessionFactory, HqlSqlWalker walker,
String catalog, String schema) {
super( sessionFactory, walker );
Dialect dialect = sessionFactory.getServiceRegistry().getService( JdbcServices.class ).getDialect();
if ( !dialect.supportsNonQueryWithCTE() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support CTE that can take UPDATE or DELETE statements as well!"
);
}
if ( !dialect.supportsValuesList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support VALUES lists!"
);
}
if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support IN clause row-value expressions (for composite identifiers)!"
);
}
this.jdbcEnvironment = sessionFactory.getServiceRegistry().getService(
JdbcServices.class ).getJdbcEnvironment();
this.catalog = catalog;
this.schema = schema;
}
protected String determineIdTableName(Queryable persister) {
return jdbcEnvironment.getQualifiedObjectNameFormatter().format(
new QualifiedTableName(
Identifier.toIdentifier( catalog ),
Identifier.toIdentifier( schema ),
Identifier.toIdentifier( "HT_" + persister.getTableName() )
),
jdbcEnvironment.getDialect()
);
}
protected String generateIdSubselect(Queryable persister) {
return new StringBuilder()
.append( "select " )
.append( String.join(
", ",
(CharSequence[]) persister.getIdentifierColumnNames()
) )
.append( " from " )
.append( determineIdTableName( persister ) )
.toString();
}
protected CteValuesListBuilder prepareCteStatement(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
return new CteValuesListBuilder(
determineIdTableName( getTargetedQueryable() ),
getTargetedQueryable().getIdentifierColumnNames(),
selectIds( session, queryParameters )
);
}
}

View File

@ -0,0 +1,68 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.cte;
import java.util.Collections;
import java.util.List;
/**
* Builds the CTE with VALUES list clause that wraps the identifiers to be updated/deleted.
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
*/
public class CteValuesListBuilder {
private final String tableName;
private final String[] columns;
private final List<Object[]> ids;
private String cteStatement;
public CteValuesListBuilder(
String tableName,
String[] columns,
List<Object[]> ids) {
this.tableName = tableName;
this.columns = columns;
this.ids = ids;
this.cteStatement = buildStatement();
}
public List<Object[]> getIds() {
return ids;
}
public String toStatement(String statement) {
return cteStatement + statement;
}
private String buildStatement() {
String columnNames = String.join(",", columns);
String singleIdValuesParam = '(' + String.join( ",", Collections.nCopies( columns.length, "?")) + ')';
String parameters = String.join(",", Collections.nCopies(ids.size(), singleIdValuesParam));
return new StringBuilder()
.append( "with " )
.append( tableName )
.append( " (" )
.append( columnNames )
.append( " ) as ( select " )
.append( columnNames )
.append( " from ( values " )
.append( parameters )
.append( ") as HT " )
.append( "(" )
.append( columnNames )
.append( ") ) " )
.toString();
}
}

View File

@ -0,0 +1,83 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.cte;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* This bulk-id strategy uses a CTE with a VALUE list to hold the identifiers,
* which are later used by the update or delete statement:
*
* <pre>
* with HT_Person (id ) as (
* select
* id
* from (
* values
* (?),
* (?),
* (?)
* (?)
* ) as HT (id)
* )
* delete
* from
* Person
* where
* ( id ) in (
* select
* id
* from
* HT_Person
* )
* </pre>
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
*/
public class CteValuesListBulkIdStrategy
implements MultiTableBulkIdStrategy {
public static final CteValuesListBulkIdStrategy INSTANCE = new CteValuesListBulkIdStrategy();
@Override
public void prepare(
JdbcServices jdbcServices,
JdbcConnectionAccess jdbcConnectionAccess,
MetadataImplementor metadataImplementor,
SessionFactoryOptions sessionFactoryOptions) {
// nothing to do
}
@Override
public void release(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess) {
// nothing to do
}
@Override
public UpdateHandler buildUpdateHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new CteValuesListUpdateHandlerImpl( factory, walker );
}
@Override
public DeleteHandler buildDeleteHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new CteValuesListDeleteHandlerImpl( factory, walker );
}
}

View File

@ -0,0 +1,135 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.cte;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.persister.collection.AbstractCollectionPersister;
import org.hibernate.sql.Delete;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type;
/**
* Bulk-id delete handler that uses CTE and VALUES lists.
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
*/
public class CteValuesListDeleteHandlerImpl
extends AbstractCteValuesListBulkIdHandler
implements MultiTableBulkIdStrategy.DeleteHandler {
private final List<String> deletes = new ArrayList<>();
public CteValuesListDeleteHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
this( factory, walker, null, null );
}
public CteValuesListDeleteHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker,
String catalog,
String schema) {
super( factory, walker, catalog, schema );
final String idSubselect = generateIdSubselect( getTargetedQueryable() );
for ( Type type : getTargetedQueryable().getPropertyTypes() ) {
if ( type.isCollectionType() ) {
CollectionType cType = (CollectionType) type;
AbstractCollectionPersister cPersister = (AbstractCollectionPersister) factory.getMetamodel().collectionPersister( cType.getRole() );
if ( cPersister.isManyToMany() ) {
deletes.add( generateDelete(
cPersister.getTableName(),
cPersister.getKeyColumnNames(),
idSubselect,
"bulk delete - m2m join table cleanup"
) );
}
}
}
String[] tableNames = getTargetedQueryable().getConstraintOrderedTableNameClosure();
String[][] columnNames = getTargetedQueryable().getContraintOrderedTableKeyColumnClosure();
for ( int i = 0; i < tableNames.length; i++ ) {
// TODO : an optimization here would be to consider cascade deletes and not gen those delete statements;
// the difficulty is the ordering of the tables here vs the cascade attributes on the persisters ->
// the table info gotten here should really be self-contained (i.e., a class representation
// defining all the needed attributes), then we could then get an array of those
deletes.add( generateDelete( tableNames[i], columnNames[i], idSubselect, "bulk delete" ) );
}
}
@Override
public int execute(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
CteValuesListBuilder values = prepareCteStatement( session, queryParameters );
if ( !values.getIds().isEmpty() ) {
// Start performing the deletes
for ( String deleteSuffix : deletes ) {
if ( deleteSuffix == null) {
continue;
}
String delete = values.toStatement( deleteSuffix );
try {
try ( PreparedStatement ps = session
.getJdbcCoordinator().getStatementPreparer()
.prepareStatement( delete, false ) ) {
int pos = 1;
for ( Object[] result : values.getIds() ) {
for ( Object column : result ) {
ps.setObject( pos++, column );
}
}
session
.getJdbcCoordinator().getResultSetReturn()
.executeUpdate( ps );
}
}
catch ( SQLException e ) {
throw convert( e, "error performing bulk delete", delete );
}
}
}
return values.getIds().size();
}
private String generateDelete(
String tableName,
String[] columnNames,
String idSubselect,
String comment) {
final Delete delete = new Delete().setTableName( tableName ).setWhere(
"(" + String.join( ", ", (CharSequence[]) columnNames ) + ") in ("
+ idSubselect + ")" );
if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) {
delete.setComment( comment );
}
return delete.toStatementString();
}
@Override
public String[] getSqlStatements() {
return deletes.toArray( new String[deletes.size()] );
}
}

View File

@ -0,0 +1,139 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.cte;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.tree.AssignmentSpecification;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.sql.Update;
/**
* Bulk-id update handler that uses CTE and VALUES lists.
*
* @author Evandro Pires da Silva
* @author Vlad Mihalcea
*/
public class CteValuesListUpdateHandlerImpl
extends AbstractCteValuesListBulkIdHandler
implements MultiTableBulkIdStrategy.UpdateHandler {
private final String[] updates;
private final ParameterSpecification[][] assignmentParameterSpecifications;
public CteValuesListUpdateHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
this( factory, walker, null, null );
}
public CteValuesListUpdateHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker,
String catalog,
String schema) {
super( factory, walker, catalog, schema );
String[] tableNames = getTargetedQueryable().getConstraintOrderedTableNameClosure();
String[][] columnNames = getTargetedQueryable().getContraintOrderedTableKeyColumnClosure();
String idSubselect = generateIdSubselect( getTargetedQueryable() );
updates = new String[tableNames.length];
assignmentParameterSpecifications = new ParameterSpecification[tableNames.length][];
for ( int tableIndex = 0; tableIndex < tableNames.length; tableIndex++ ) {
boolean affected = false;
final List<ParameterSpecification> parameterList = new ArrayList<>();
final Update update = new Update( factory.getServiceRegistry().getService( JdbcServices.class ).getDialect() )
.setTableName( tableNames[tableIndex] )
.setWhere( "(" + String.join( ", ", (CharSequence[]) columnNames[tableIndex] ) + ") in (" + idSubselect + ")" );
if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) {
update.setComment( "bulk update" );
}
final List<AssignmentSpecification> assignmentSpecifications = walker.getAssignmentSpecifications();
for ( AssignmentSpecification assignmentSpecification : assignmentSpecifications ) {
if ( assignmentSpecification.affectsTable( tableNames[tableIndex] ) ) {
affected = true;
update.appendAssignmentFragment( assignmentSpecification.getSqlAssignmentFragment() );
if ( assignmentSpecification.getParameters() != null ) {
Collections.addAll( parameterList, assignmentSpecification.getParameters() );
}
}
}
if ( affected ) {
updates[tableIndex] = update.toStatementString();
assignmentParameterSpecifications[tableIndex] = parameterList.toArray( new ParameterSpecification[parameterList.size()] );
}
}
}
@Override
public String[] getSqlStatements() {
return updates;
}
@Override
public int execute(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
CteValuesListBuilder values = prepareCteStatement( session, queryParameters );
if ( !values.getIds().isEmpty() ) {
// Start performing the updates
for ( int i = 0; i < updates.length; i++ ) {
String updateSuffix = updates[i];
if ( updateSuffix == null) {
continue;
}
String update = values.toStatement( updateSuffix );
try {
try (PreparedStatement ps = session
.getJdbcCoordinator().getStatementPreparer()
.prepareStatement( update, false )) {
int position = 1; // jdbc params are 1-based
for ( Object[] result : values.getIds() ) {
for ( Object column : result ) {
ps.setObject( position++, column );
}
}
if ( assignmentParameterSpecifications[i] != null ) {
for ( int x = 0; x < assignmentParameterSpecifications[i].length; x++ ) {
position += assignmentParameterSpecifications[i][x]
.bind( ps, queryParameters, session,
position
);
}
}
session
.getJdbcCoordinator().getResultSetReturn()
.executeUpdate( ps );
}
}
catch ( SQLException e ) {
throw convert(
e,
"error performing bulk update",
update
);
}
}
}
return values.getIds().size();
}
}

View File

@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.AbstractIdsBulkIdHandler;
/**
* Base class for all bulk-id strategy handlers that inline the identifiers of the updatable/deletable rows.
*
* @author Vlad Mihalcea
*/
public abstract class AbstractInlineIdsBulkIdHandler
extends AbstractIdsBulkIdHandler {
public AbstractInlineIdsBulkIdHandler(
SessionFactoryImplementor sessionFactory,
HqlSqlWalker walker) {
super( sessionFactory, walker );
}
protected IdsClauseBuilder prepareInlineStatement(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
return newIdsClauseBuilder( selectIds( session, queryParameters ) );
}
protected abstract IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids);
}

View File

@ -0,0 +1,118 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.persister.collection.AbstractCollectionPersister;
import org.hibernate.sql.Delete;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type;
/**
* Inline bulk-id delete handler that selects the identifiers of the rows to be updated.
*
* @author Vlad Mihalcea
*/
public abstract class AbstractInlineIdsDeleteHandlerImpl
extends AbstractInlineIdsBulkIdHandler
implements MultiTableBulkIdStrategy.DeleteHandler {
private final List<String> deletes = new ArrayList<>();
public AbstractInlineIdsDeleteHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
}
@Override
public String[] getSqlStatements() {
return deletes.toArray( new String[deletes.size()] );
}
@Override
public int execute(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
IdsClauseBuilder values = prepareInlineStatement( session, queryParameters );
if ( !values.getIds().isEmpty() ) {
final String idSubselect = values.toStatement();
for ( Type type : getTargetedQueryable().getPropertyTypes() ) {
if ( type.isCollectionType() ) {
CollectionType cType = (CollectionType) type;
AbstractCollectionPersister cPersister = (AbstractCollectionPersister) factory().getMetamodel().collectionPersister( cType.getRole() );
if ( cPersister.isManyToMany() ) {
deletes.add( generateDelete(
cPersister.getTableName(),
cPersister.getKeyColumnNames(),
idSubselect,
"bulk delete - m2m join table cleanup"
).toStatementString() );
}
}
}
String[] tableNames = getTargetedQueryable().getConstraintOrderedTableNameClosure();
String[][] columnNames = getTargetedQueryable().getContraintOrderedTableKeyColumnClosure();
for ( int i = 0; i < tableNames.length; i++ ) {
// TODO : an optimization here would be to consider cascade deletes and not gen those delete statements;
// the difficulty is the ordering of the tables here vs the cascade attributes on the persisters ->
// the table info gotten here should really be self-contained (i.e., a class representation
// defining all the needed attributes), then we could then get an array of those
deletes.add( generateDelete( tableNames[i], columnNames[i], idSubselect, "bulk delete" ).toStatementString() );
}
// Start performing the deletes
for ( String delete : deletes ) {
if ( delete == null) {
continue;
}
try {
try ( PreparedStatement ps = session
.getJdbcCoordinator().getStatementPreparer()
.prepareStatement( delete, false ) ) {
session
.getJdbcCoordinator().getResultSetReturn()
.executeUpdate( ps );
}
}
catch ( SQLException e ) {
throw convert( e, "error performing bulk delete", delete );
}
}
}
return values.getIds().size();
}
protected Delete generateDelete(
String tableName,
String[] columnNames,
String idSubselect,
String comment) {
final Delete delete = new Delete().setTableName( tableName ).setWhere(
"(" + String.join( ", ", (CharSequence[]) columnNames ) + ") in ("
+ idSubselect + ")" );
if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) {
delete.setComment( comment );
}
return delete;
}
}

View File

@ -0,0 +1,140 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.tree.AssignmentSpecification;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.sql.Update;
/**
* Inline bulk-id delete handler that selects the identifiers of the rows to be deleted.
*
* @author Vlad Mihalcea
*/
public abstract class AbstractInlineIdsUpdateHandlerImpl
extends AbstractInlineIdsBulkIdHandler
implements MultiTableBulkIdStrategy.UpdateHandler {
private final Map<Integer, String> updates = new LinkedHashMap<>();
private ParameterSpecification[][] assignmentParameterSpecifications;
public AbstractInlineIdsUpdateHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
}
@Override
public String[] getSqlStatements() {
return updates.values().toArray( new String[updates.values().size()] );
}
@Override
public int execute(
SharedSessionContractImplementor session,
QueryParameters queryParameters) {
IdsClauseBuilder values = prepareInlineStatement( session, queryParameters );
if ( !values.getIds().isEmpty() ) {
String[] tableNames = getTargetedQueryable().getConstraintOrderedTableNameClosure();
String[][] columnNames = getTargetedQueryable().getContraintOrderedTableKeyColumnClosure();
String idSubselect = values.toStatement();
assignmentParameterSpecifications = new ParameterSpecification[tableNames.length][];
for ( int tableIndex = 0; tableIndex < tableNames.length; tableIndex++ ) {
boolean affected = false;
final List<ParameterSpecification> parameterList = new ArrayList<>();
Update update = generateUpdate( tableNames[tableIndex], columnNames[tableIndex], idSubselect, "bulk update" );
final List<AssignmentSpecification> assignmentSpecifications = walker().getAssignmentSpecifications();
for ( AssignmentSpecification assignmentSpecification : assignmentSpecifications ) {
if ( assignmentSpecification.affectsTable( tableNames[tableIndex] ) ) {
affected = true;
update.appendAssignmentFragment( assignmentSpecification.getSqlAssignmentFragment() );
if ( assignmentSpecification.getParameters() != null ) {
Collections.addAll( parameterList, assignmentSpecification.getParameters() );
}
}
}
if ( affected ) {
updates.put( tableIndex, update.toStatementString() );
assignmentParameterSpecifications[tableIndex] = parameterList.toArray( new ParameterSpecification[parameterList.size()] );
}
}
// Start performing the updates
for ( Map.Entry<Integer, String> updateEntry: updates.entrySet()) {
int i = updateEntry.getKey();
String update = updateEntry.getValue();
if ( update == null) {
continue;
}
try {
try (PreparedStatement ps = session
.getJdbcCoordinator().getStatementPreparer()
.prepareStatement( update, false )) {
int position = 1; // jdbc params are 1-based
if ( assignmentParameterSpecifications[i] != null ) {
for ( int x = 0; x < assignmentParameterSpecifications[i].length; x++ ) {
position += assignmentParameterSpecifications[i][x]
.bind( ps, queryParameters, session,
position
);
}
}
session
.getJdbcCoordinator().getResultSetReturn()
.executeUpdate( ps );
}
}
catch ( SQLException e ) {
throw convert(
e,
"error performing bulk update",
update
);
}
}
}
return values.getIds().size();
}
protected Update generateUpdate(
String tableName,
String[] columnNames,
String idSubselect,
String comment) {
final Update update = new Update( factory().getServiceRegistry().getService( JdbcServices.class ).getDialect() )
.setTableName( tableName )
.setWhere( "(" + String.join( ", ", (CharSequence[]) columnNames ) + ") in (" + idSubselect + ")" );
if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) {
update.setComment( comment );
}
return update;
}
}

View File

@ -0,0 +1,95 @@
package org.hibernate.hql.spi.id.inline;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.CompositeType;
import org.hibernate.type.LiteralType;
import org.hibernate.type.Type;
import org.hibernate.type.TypeResolver;
/**
* Builds the where clause that wraps the identifiers to be updated/deleted.
*
* @author Vlad Mihalcea
*/
public abstract class IdsClauseBuilder {
private final Dialect dialect;
private final Type identifierType;
private final TypeResolver typeResolver;
private final String[] columns;
private final List<Object[]> ids;
protected IdsClauseBuilder(
Dialect dialect,
Type identifierType,
TypeResolver typeResolver,
String[] columns,
List<Object[]> ids) {
this.dialect = dialect;
this.identifierType = identifierType;
this.typeResolver = typeResolver;
this.columns = columns;
this.ids = ids;
}
public Type getIdentifierType() {
return identifierType;
}
public TypeResolver getTypeResolver() {
return typeResolver;
}
protected String[] getColumns() {
return columns;
}
public List<Object[]> getIds() {
return ids;
}
public abstract String toStatement();
protected String quoteIdentifier(Object... value) {
if ( value.length == 1 ) {
return quoteIdentifier( value[0], identifierType );
}
else {
if ( identifierType instanceof CompositeType ) {
CompositeType compositeType = (CompositeType) identifierType;
List<String> quotedIdentifiers = new ArrayList<>();
for ( int i = 0; i < value.length; i++ ) {
quotedIdentifiers.add(quoteIdentifier( value[i], compositeType.getSubtypes()[i] ));
}
return String.join( ",", quotedIdentifiers );
}
else {
throw new IllegalArgumentException("Composite identifier does not implement CompositeType");
}
}
}
private String quoteIdentifier(Object value, Type type) {
Type resolvedType = ( !type.getReturnedClass().equals( value.getClass() ) ) ?
typeResolver.heuristicType( value.getClass().getName() ) : type;
if ( resolvedType instanceof LiteralType ) {
LiteralType literalType = (LiteralType) resolvedType;
try {
return literalType.objectToSQLString( value, dialect );
}
catch ( Exception e ) {
throw new IllegalArgumentException( e );
}
}
return String.valueOf( value );
}
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* Inline bulk-id delete handler that uses an IN clause.
*
* @author Vlad Mihalcea
*/
public class InlineIdsIdsInClauseDeleteHandlerImpl
extends AbstractInlineIdsDeleteHandlerImpl
implements MultiTableBulkIdStrategy.DeleteHandler {
public InlineIdsIdsInClauseDeleteHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
Dialect dialect = factory.getServiceRegistry().getService( JdbcServices.class ).getDialect();
if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support IN clause row-value expressions (for composite identifiers)!"
);
}
}
@Override
protected IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids) {
return new InlineIdsInClauseBuilder(
dialect(),
getTargetedQueryable().getIdentifierType(),
factory().getTypeResolver(),
getTargetedQueryable().getIdentifierColumnNames(),
ids
);
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.sql.Delete;
/**
* Inline bulk-id delete handler that uses multiple identifier OR clauses.
*
* @author Vlad Mihalcea
*/
public class InlineIdsIdsOrClauseDeleteHandlerImpl
extends AbstractInlineIdsDeleteHandlerImpl
implements MultiTableBulkIdStrategy.DeleteHandler {
public InlineIdsIdsOrClauseDeleteHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
}
@Override
protected IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids) {
return new InlineIdsOrClauseBuilder(
dialect(),
getTargetedQueryable().getIdentifierType(),
factory().getTypeResolver(),
getTargetedQueryable().getIdentifierColumnNames(),
ids
);
}
protected Delete generateDelete(
String tableName,
String[] columnNames,
String idSubselect,
String comment) {
final Delete delete = new Delete()
.setTableName( tableName )
.setWhere(idSubselect);
if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) {
delete.setComment( comment );
}
return delete;
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.Type;
import org.hibernate.type.TypeResolver;
/**
* Builds the where IN clause that wraps the identifiers to be updated/deleted.
*
* @author Vlad Mihalcea
*/
public class InlineIdsInClauseBuilder extends IdsClauseBuilder {
private final int chunkLimit;
public InlineIdsInClauseBuilder(
Dialect dialect, Type identifierType, TypeResolver typeResolver, String[] columns, List<Object[]> ids) {
super( dialect, identifierType, typeResolver, columns, ids );
this.chunkLimit = dialect.getInExpressionCountLimit();
}
@Override
public String toStatement() {
StringBuilder buffer = new StringBuilder();
String columnNames = String.join( ",", (CharSequence[]) getColumns() );
for ( int i = 0; i < getIds().size(); i++ ) {
Object[] idTokens = getIds().get( i );
if ( i > 0 ) {
if( chunkLimit > 0 && i % chunkLimit == 0 ) {
buffer.append( " ) or ( " );
buffer.append( columnNames );
buffer.append( " ) in (" );
}
else {
buffer.append( "," );
}
}
buffer.append( "(" );
buffer.append( quoteIdentifier( idTokens ) );
buffer.append( ")" );
}
return buffer.toString();
}
}

View File

@ -0,0 +1,71 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* This bulk-id strategy inlines identifiers of the rows that need to be updated or deleted using an IN clause:
*
* <pre>
* delete
* from
* Doctor
* where
* ( id ) in (
* ( 1 ),
* ( 2 ),
* ( 3 ),
* ( 4 )
* )
* </pre>
*
* @author Vlad Mihalcea
*/
public class InlineIdsInClauseBulkIdStrategy
implements MultiTableBulkIdStrategy {
public static final InlineIdsInClauseBulkIdStrategy INSTANCE =
new InlineIdsInClauseBulkIdStrategy();
@Override
public void prepare(
JdbcServices jdbcServices,
JdbcConnectionAccess jdbcConnectionAccess,
MetadataImplementor metadataImplementor,
SessionFactoryOptions sessionFactoryOptions) {
// nothing to do
}
@Override
public void release(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess) {
// nothing to do
}
@Override
public UpdateHandler buildUpdateHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new InlineIdsInClauseUpdateHandlerImpl( factory, walker );
}
@Override
public DeleteHandler buildDeleteHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new InlineIdsIdsInClauseDeleteHandlerImpl( factory, walker );
}
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* Inline bulk-id update handler that uses an IN clause.
*
* @author Vlad Mihalcea
*/
public class InlineIdsInClauseUpdateHandlerImpl
extends AbstractInlineIdsUpdateHandlerImpl
implements MultiTableBulkIdStrategy.UpdateHandler {
public InlineIdsInClauseUpdateHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
Dialect dialect = factory.getServiceRegistry().getService( JdbcServices.class ).getDialect();
if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support IN clause row-value expressions (for composite identifiers)!"
);
}
}
@Override
protected IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids) {
return new InlineIdsInClauseBuilder(
dialect(),
getTargetedQueryable().getIdentifierType(),
factory().getTypeResolver(),
getTargetedQueryable().getIdentifierColumnNames(),
ids
);
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.Type;
import org.hibernate.type.TypeResolver;
/**
* Builds the where clause using OR expressions for the identifiers to be updated/deleted.
* This is useful for Dialects that do no support IN clause row value expressions.
*
* @author Vlad Mihalcea
*/
public class InlineIdsOrClauseBuilder extends IdsClauseBuilder {
public InlineIdsOrClauseBuilder(
Dialect dialect, Type identifierType, TypeResolver typeResolver, String[] columns, List<Object[]> ids) {
super( dialect, identifierType, typeResolver, columns, ids );
}
@Override
public String toStatement() {
StringBuilder buffer = new StringBuilder();
for ( int i = 0; i < getIds().size(); i++ ) {
Object[] idTokens = getIds().get( i );
if ( i > 0 ) {
buffer.append( " or " );
}
buffer.append( "(" );
for ( int j = 0; j < getColumns().length; j++ ) {
if ( j > 0 ) {
buffer.append( " and " );
}
buffer.append( getColumns()[j] );
buffer.append( " = " );
buffer.append( quoteIdentifier( idTokens[j] ) );
}
buffer.append( ")" );
}
return buffer.toString();
}
}

View File

@ -0,0 +1,73 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* This bulk-id strategy inlines identifiers of the rows that need to be updated or deleted using multiple identifier OR clauses.
*
* <pre>
* delete
* from
* Engineer
* where
* (
* id = 0
* and companyName = 'Red Hat Europe'
* )
* or (
* id = 1
* and companyName = 'Red Hat USA'
* )
* </pre>
*
* @author Vlad Mihalcea
*/
public class InlineIdsOrClauseBulkIdStrategy
implements MultiTableBulkIdStrategy {
public static final InlineIdsOrClauseBulkIdStrategy INSTANCE =
new InlineIdsOrClauseBulkIdStrategy();
@Override
public void prepare(
JdbcServices jdbcServices,
JdbcConnectionAccess jdbcConnectionAccess,
MetadataImplementor metadataImplementor,
SessionFactoryOptions sessionFactoryOptions) {
// nothing to do
}
@Override
public void release(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess) {
// nothing to do
}
@Override
public UpdateHandler buildUpdateHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new InlineIdsOrClauseUpdateHandlerImpl( factory, walker );
}
@Override
public DeleteHandler buildDeleteHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new InlineIdsIdsOrClauseDeleteHandlerImpl( factory, walker );
}
}

View File

@ -0,0 +1,57 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.sql.Update;
/**
* Inline bulk-id update handler that uses multiple identifier OR clauses.
*
* @author Vlad Mihalcea
*/
public class InlineIdsOrClauseUpdateHandlerImpl
extends AbstractInlineIdsUpdateHandlerImpl
implements MultiTableBulkIdStrategy.UpdateHandler {
public InlineIdsOrClauseUpdateHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
}
@Override
protected IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids) {
return new InlineIdsOrClauseBuilder(
dialect(),
getTargetedQueryable().getIdentifierType(),
factory().getTypeResolver(),
getTargetedQueryable().getIdentifierColumnNames(),
ids
);
}
@Override
protected Update generateUpdate(
String tableName,
String[] columnNames,
String idSubselect,
String comment) {
final Update update = new Update( factory().getServiceRegistry().getService( JdbcServices.class ).getDialect() )
.setTableName( tableName )
.setWhere( idSubselect );
if ( factory().getSessionFactoryOptions().isCommentsEnabled() ) {
update.setComment( comment );
}
return update;
}
}

View File

@ -0,0 +1,82 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* This bulk-id strategy inlines identifiers of the rows that need to be updated or deleted in a subselect using a VALUES list:
*
* <pre>
* delete
* from
* Person
* where
* ( id ) in (
* select
* id
* from (
* values
* ( 1 ),
* ( 2 ),
* ( 3 ),
* ( 4 )
* ) as HT (id)
* )
* </pre>
*
* @author Vlad Mihalcea
*/
public class InlineIdsSubSelectValueListBulkIdStrategy
implements MultiTableBulkIdStrategy {
public static final InlineIdsSubSelectValueListBulkIdStrategy INSTANCE =
new InlineIdsSubSelectValueListBulkIdStrategy();
@Override
public void prepare(
JdbcServices jdbcServices,
JdbcConnectionAccess jdbcConnectionAccess,
MetadataImplementor metadataImplementor,
SessionFactoryOptions sessionFactoryOptions) {
// nothing to do
}
@Override
public void release(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess) {
// nothing to do
}
@Override
public UpdateHandler buildUpdateHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new InlineIdsSubSelectValuesListUpdateHandlerImpl(
factory,
walker
);
}
@Override
public DeleteHandler buildDeleteHandler(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
return new InlineIdsSubSelectValuesListDeleteHandlerImpl(
factory,
walker
);
}
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.Type;
import org.hibernate.type.TypeResolver;
/**
* Builds the where SELECT FROM VALUES clause that wraps the identifiers to be updated/deleted.
*
* @author Vlad Mihalcea
*/
public class InlineIdsSubSelectValuesListBuilder extends IdsClauseBuilder {
public InlineIdsSubSelectValuesListBuilder(
Dialect dialect, Type identifierType, TypeResolver typeResolver, String[] columns, List<Object[]> ids) {
super( dialect, identifierType, typeResolver, columns, ids );
}
@Override
public String toStatement() {
StringBuilder buffer = new StringBuilder();
String columnNames = String.join( ",", (CharSequence[]) getColumns() );
buffer.append( "select " ).append( columnNames ).append(
" from ( values " );
for ( int i = 0; i < getIds().size(); i++ ) {
Object[] idTokens = getIds().get( i );
if ( i > 0 ) {
buffer.append( "," );
}
buffer.append( "(" );
buffer.append( quoteIdentifier( idTokens ) );
buffer.append( ")" );
}
buffer.append( ") as HT (" ).append( columnNames ).append( ") " );
return buffer.toString();
}
}

View File

@ -0,0 +1,56 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* Inline bulk-id delete handler that uses a subselect with a VALUES clause.
*
* @author Vlad Mihalcea
*/
public class InlineIdsSubSelectValuesListDeleteHandlerImpl
extends AbstractInlineIdsDeleteHandlerImpl
implements MultiTableBulkIdStrategy.DeleteHandler {
public InlineIdsSubSelectValuesListDeleteHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
Dialect dialect = factory().getServiceRegistry().getService( JdbcServices.class ).getDialect();
if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support IN clause row-value expressions (for composite identifiers)!"
);
}
if ( !dialect.supportsValuesList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support VALUES lists!"
);
}
}
@Override
protected IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids) {
return new InlineIdsSubSelectValuesListBuilder(
dialect(),
getTargetedQueryable().getIdentifierType(),
factory().getTypeResolver(),
getTargetedQueryable().getIdentifierColumnNames(),
ids
);
}
}

View File

@ -0,0 +1,56 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.hql.spi.id.inline;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
/**
* Inline bulk-id update handler that uses a subselect with a VALUES clause.
*
* @author Vlad Mihalcea
*/
public class InlineIdsSubSelectValuesListUpdateHandlerImpl
extends AbstractInlineIdsUpdateHandlerImpl
implements MultiTableBulkIdStrategy.DeleteHandler {
public InlineIdsSubSelectValuesListUpdateHandlerImpl(
SessionFactoryImplementor factory,
HqlSqlWalker walker) {
super( factory, walker );
Dialect dialect = factory().getServiceRegistry().getService( JdbcServices.class ).getDialect();
if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support IN clause row-value expressions (for composite identifiers)!"
);
}
if ( !dialect.supportsValuesList() ) {
throw new UnsupportedOperationException(
"The " + getClass().getSimpleName() +
" can only be used with Dialects that support VALUES lists!"
);
}
}
@Override
protected IdsClauseBuilder newIdsClauseBuilder(List<Object[]> ids) {
return new InlineIdsSubSelectValuesListBuilder(
dialect(),
getTargetedQueryable().getIdentifierType(),
factory().getTypeResolver(),
getTargetedQueryable().getIdentifierColumnNames(),
ids
);
}
}

View File

@ -0,0 +1,197 @@
package org.hibernate.test.bulkid;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public abstract class AbstractBulkCompositeIdTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Doctor.class,
Engineer.class
};
}
@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
configuration.setProperty( AvailableSettings.HQL_BULK_ID_STRATEGY, getMultiTableBulkIdStrategyClass().getName() );
return configuration;
}
protected abstract Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass();
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Override
protected boolean isCleanupTestDataUsingBulkDelete() {
return true;
}
@Before
public void setUp() {
doInHibernate( this::sessionFactory, session -> {
for ( int i = 0; i < entityCount(); i++ ) {
Doctor doctor = new Doctor();
doctor.setId( i );
doctor.setCompanyName( "Red Hat USA" );
doctor.setEmployed( ( i % 2 ) == 0 );
session.persist( doctor );
}
for ( int i = 0; i < entityCount(); i++ ) {
Engineer engineer = new Engineer();
engineer.setId( i );
engineer.setCompanyName( "Red Hat Europe" );
engineer.setEmployed( ( i % 2 ) == 0 );
engineer.setFellow( ( i % 2 ) == 1 );
session.persist( engineer );
}
});
}
protected int entityCount() {
return 100;
}
@Test
public void testUpdate() {
doInHibernate( this::sessionFactory, session -> {
int updateCount = session.createQuery( "update Person set name = :name where employed = :employed" )
.setParameter( "name", "John Doe" )
.setParameter( "employed", true )
.executeUpdate();
assertEquals(entityCount(), updateCount);
});
}
@Test
public void testDeleteFromPerson() {
doInHibernate( this::sessionFactory, session -> {
int updateCount = session.createQuery( "delete from Person where employed = :employed" )
.setParameter( "employed", false )
.executeUpdate();
assertEquals( entityCount(), updateCount );
});
}
@Test
public void testDeleteFromEngineer() {
doInHibernate( this::sessionFactory, session -> {
int updateCount = session.createQuery( "delete from Engineer where fellow = :fellow" )
.setParameter( "fellow", true )
.executeUpdate();
assertEquals( entityCount() / 2, updateCount );
});
}
@Entity(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Person implements Serializable {
@Id
private Integer id;
@Id
private String companyName;
private String name;
private boolean employed;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEmployed() {
return employed;
}
public void setEmployed(boolean employed) {
this.employed = employed;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( !( o instanceof Person ) ) {
return false;
}
Person person = (Person) o;
return Objects.equals( getId(), person.getId() ) &&
Objects.equals( getCompanyName(), person.getCompanyName() );
}
@Override
public int hashCode() {
return Objects.hash( getId(), getCompanyName() );
}
}
@Entity(name = "Doctor")
public static class Doctor extends Person {
}
@Entity(name = "Engineer")
public static class Engineer extends Person {
private boolean fellow;
public boolean isFellow() {
return fellow;
}
public void setFellow(boolean fellow) {
this.fellow = fellow;
}
}
}

View File

@ -0,0 +1,163 @@
package org.hibernate.test.bulkid;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public abstract class AbstractBulkIdTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Doctor.class,
Engineer.class
};
}
@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
configuration.setProperty( AvailableSettings.HQL_BULK_ID_STRATEGY, getMultiTableBulkIdStrategyClass().getName() );
return configuration;
}
protected abstract Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass();
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Override
protected boolean isCleanupTestDataUsingBulkDelete() {
return true;
}
@Before
public void setUp() {
doInHibernate( this::sessionFactory, session -> {
for ( int i = 0; i < entityCount(); i++ ) {
Doctor doctor = new Doctor();
doctor.setEmployed( ( i % 2 ) == 0 );
session.persist( doctor );
}
for ( int i = 0; i < entityCount(); i++ ) {
Engineer engineer = new Engineer();
engineer.setEmployed( ( i % 2 ) == 0 );
engineer.setFellow( ( i % 2 ) == 1 );
session.persist( engineer );
}
});
}
protected int entityCount() {
return 100;
}
@Test
public void testUpdate() {
doInHibernate( this::sessionFactory, session -> {
int updateCount = session.createQuery( "update Person set name = :name where employed = :employed" )
.setParameter( "name", "John Doe" )
.setParameter( "employed", true )
.executeUpdate();
assertEquals(entityCount(), updateCount);
});
}
@Test
public void testDeleteFromPerson() {
doInHibernate( this::sessionFactory, session -> {
int updateCount = session.createQuery( "delete from Person where employed = :employed" )
.setParameter( "employed", false )
.executeUpdate();
assertEquals( entityCount(), updateCount );
});
}
@Test
public void testDeleteFromEngineer() {
doInHibernate( this::sessionFactory, session -> {
int updateCount = session.createQuery( "delete from Engineer where fellow = :fellow" )
.setParameter( "fellow", true )
.executeUpdate();
assertEquals( entityCount() / 2, updateCount );
});
}
@Entity(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private boolean employed;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEmployed() {
return employed;
}
public void setEmployed(boolean employed) {
this.employed = employed;
}
}
@Entity(name = "Doctor")
public static class Doctor extends Person {
}
@Entity(name = "Engineer")
public static class Engineer extends Person {
private boolean fellow;
public boolean isFellow() {
return fellow;
}
public void setFellow(boolean fellow) {
this.fellow = fellow;
}
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.cte.CteValuesListBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
@RequiresDialectFeature(DialectChecks.SupportNonQueryValuesListWithCTE.class)
public class CteValuesListBulkCompositeIdTest extends AbstractBulkCompositeIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return CteValuesListBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.cte.CteValuesListBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
@RequiresDialectFeature(DialectChecks.SupportNonQueryValuesListWithCTE.class)
public class CteValuesListBulkIdTest extends AbstractBulkIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return CteValuesListBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,20 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
@RequiresDialectFeature(DialectChecks.SupportRowValueConstructorSyntaxInInList.class)
public class InlineIdsInClauseBulkCompositeIdTest extends
AbstractBulkCompositeIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return InlineIdsInClauseBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
@RequiresDialectFeature(DialectChecks.SupportRowValueConstructorSyntaxInInList.class)
public class InlineIdsInClauseBulkIdTest extends AbstractBulkIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return InlineIdsInClauseBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
public class InlineIdsOrClauseBulkCompositeIdTest extends
AbstractBulkCompositeIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return InlineIdsOrClauseBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,15 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.inline.InlineIdsOrClauseBulkIdStrategy;
/**
* @author Vlad Mihalcea
*/
public class InlineIdsOrClauseBulkIdTest extends AbstractBulkIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return InlineIdsOrClauseBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.inline.InlineIdsSubSelectValueListBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
@RequiresDialectFeature(DialectChecks.SupportValuesListAndRowValueConstructorSyntaxInInList.class)
public class InlineIdsSubSelectValuesListBulkCompositeIdTest extends AbstractBulkCompositeIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return InlineIdsSubSelectValueListBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.bulkid;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.inline.InlineIdsSubSelectValueListBulkIdStrategy;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
/**
* @author Vlad Mihalcea
*/
@RequiresDialectFeature(DialectChecks.SupportValuesListAndRowValueConstructorSyntaxInInList.class)
public class InlineIdsSubSelectValuesListBulkIdTest extends AbstractBulkIdTest {
@Override
protected Class<? extends MultiTableBulkIdStrategy> getMultiTableBulkIdStrategyClass() {
return InlineIdsSubSelectValueListBulkIdStrategy.class;
}
}

View File

@ -0,0 +1,17 @@
package org.hibernate.test.bulkid;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.testing.RequiresDialect;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(Oracle8iDialect.class)
public class OracleInlineIdsInClauseBulkIdTest extends InlineIdsInClauseBulkIdTest {
@Override
protected int entityCount() {
return 1100;
}
}

View File

@ -201,4 +201,26 @@ abstract public class DialectChecks {
return dialect.supportsPartitionBy();
}
}
public static class SupportNonQueryValuesListWithCTE implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsValuesList() &&
dialect.supportsNonQueryWithCTE() &&
dialect.supportsRowValueConstructorSyntaxInInList();
}
}
public static class SupportValuesListAndRowValueConstructorSyntaxInInList
implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsValuesList() &&
dialect.supportsRowValueConstructorSyntaxInInList();
}
}
public static class SupportRowValueConstructorSyntaxInInList implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsRowValueConstructorSyntaxInInList();
}
}
}