Skip to content

Commit

Permalink
HHH-11262 - Bulk Operations attempt to create temporary tables, but u…
Browse files Browse the repository at this point in the history
…ser does not have permission to create table
  • Loading branch information
Evandro Pires da Silva authored and vladmihalcea committed Feb 1, 2017
1 parent 8f6e4d5 commit d48f393
Show file tree
Hide file tree
Showing 43 changed files with 2,348 additions and 14 deletions.
18 changes: 18 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
Expand Up @@ -2865,6 +2865,24 @@ public boolean supportsNationalizedTypes() {
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;
}
Expand Down
Expand Up @@ -65,4 +65,12 @@ public MySQL57InnoDBDialect() {
// 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;
}
}
Expand Up @@ -56,4 +56,13 @@ public String getDropIdTableCommand() {
public String getDropSequenceString(String sequenceName) {
return "drop sequence if exists " + sequenceName;
}

@Override
public boolean supportsValuesList() {
return true;
}

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

@Override
public boolean supportsNonQueryWithCTE() {
return true;
}
}
Expand Up @@ -105,4 +105,9 @@ public JDBCException convert(SQLException sqlException, String message, String s
}
};
}

@Override
public boolean supportsNonQueryWithCTE() {
return true;
}
}
Expand Up @@ -56,4 +56,9 @@ public String renderOrderByElement(String expression, String collation, String o

return orderByElement.toString();
}

@Override
public boolean supportsValuesList() {
return true;
}
}
@@ -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;
}
}
Expand Up @@ -127,6 +127,22 @@ protected String generateIdInsertSelect(
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 );
Expand Down Expand Up @@ -160,14 +176,7 @@ protected String generateIdInsertSelect(
}
}
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;
}

/**
Expand Down
Expand Up @@ -114,10 +114,10 @@ public int execute(SharedSessionContractImplementor session, QueryParameters que
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 );
}
Expand Down
Expand Up @@ -116,10 +116,10 @@ public int execute(SharedSessionContractImplementor session, QueryParameters que
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 );
}
Expand Down
@@ -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 )
);
}
}

0 comments on commit d48f393

Please sign in to comment.