HHH-3326 fix temp table collation issue for SQLServer dialect

This commit is contained in:
Nathan Xu 2020-08-13 14:20:49 -04:00 committed by Christian Beikov
parent fe3f27a733
commit 00d9c12f05
4 changed files with 196 additions and 0 deletions

View File

@ -3050,4 +3050,14 @@ public abstract class Dialect implements ConversionContext {
public boolean supportsSelectAliasInGroupByClause() {
return false;
}
/**
* Annotation to be appended to the end of each COLUMN clause for temporary tables.
*
* @param sqlTypeCode The SQL type code
* @return The annotation to be appended (e.g. "COLLATE DATABASE_DEFAULT" in SQLServer SQL)
*/
public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) {
return "";
}
}

View File

@ -20,6 +20,8 @@ import org.hibernate.dialect.pagination.LegacyLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.TopLimitHandler;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
@ -214,4 +216,19 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
public IdentityColumnSupport getIdentityColumnSupport() {
return new SQLServerIdentityColumnSupport();
}
@Override
public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) {
switch (sqlTypeCode) {
case Types.CHAR:
case Types.NCHAR:
case Types.VARCHAR:
case Types.NVARCHAR:
case Types.LONGVARCHAR:
case Types.LONGNVARCHAR:
return "collate database_default";
default:
return "";
}
}
}

View File

@ -142,12 +142,20 @@ public abstract class AbstractMultiTableBulkIdStrategyImpl<TT extends IdTableInf
final Column column = (Column) itr.next();
buffer.append( column.getQuotedName( dialect ) ).append( ' ' );
buffer.append( column.getSqlType( dialect, metadata ) );
final int sqlTypeCode = column.getSqlTypeCode( metadata );
final String columnAnnotation = dialect.getCreateTemporaryTableColumnAnnotation( sqlTypeCode );
if ( !columnAnnotation.isEmpty() ) {
buffer.append(" ").append( columnAnnotation );
}
if ( column.isNullable() ) {
buffer.append( dialect.getNullColumnString() );
}
else {
buffer.append( " not null" );
}
if ( itr.hasNext() ) {
buffer.append( ", " );
}

View File

@ -0,0 +1,161 @@
/*
* 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.test.dialect.functional;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.SQLServer2005Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.testing.AfterClassOnce;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Assert;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
/**
* @author Nathan Xu
*/
@RequiresDialect( SQLServer2005Dialect.class )
@TestForIssue( jiraKey = "HHH-3326" )
public class SQLServerDialectTempTableCollationTest extends BaseCoreFunctionalTestCase {
private String originalDBCollation;
private final String changedDBCollation = "SQL_Latin1_General_CP437_BIN";
private boolean collationChanged;
@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
configuration.setProperty( AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, Boolean.TRUE.toString() );
return configuration;
}
@AfterClassOnce
protected void revertBackOriginalDBCollation() {
if ( originalDBCollation != null && collationChanged && !changedDBCollation.equals( originalDBCollation ) ) {
BootstrapServiceRegistry bootRegistry = buildBootstrapServiceRegistry();
StandardServiceRegistryImpl serviceRegistry = buildServiceRegistry(
bootRegistry,
constructConfiguration()
);
try (Connection connection = serviceRegistry.getService( JdbcServices.class )
.getBootstrapJdbcConnectionAccess()
.obtainConnection();
Statement statement = connection.createStatement()) {
connection.setAutoCommit( true );
statement.executeUpdate( "ALTER DATABASE CURRENT COLLATE " + originalDBCollation );
}
catch (SQLException e) {
throw new RuntimeException( "Failed to revert back database collation to " + originalDBCollation, e );
}
finally {
serviceRegistry.destroy();
}
}
}
protected void buildSessionFactory() {
BootstrapServiceRegistry bootRegistry = buildBootstrapServiceRegistry();
StandardServiceRegistryImpl serviceRegistry = buildServiceRegistry( bootRegistry, constructConfiguration() );
try {
try ( Connection connection = serviceRegistry.getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess().obtainConnection();
Statement statement = connection.createStatement() ) {
connection.setAutoCommit( true );
try ( ResultSet rs = statement.executeQuery( "SELECT SERVERPROPERTY('collation')" ) ) {
rs.next();
String instanceCollation = rs.getString( 1 );
Assert.assertNotEquals( instanceCollation, changedDBCollation );
}
}
catch (SQLException e) {
log.debug( e.getMessage() );
}
try ( Connection connection = serviceRegistry.getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess().obtainConnection();
Statement statement = connection.createStatement() ) {
connection.setAutoCommit( true );
try ( ResultSet rs = statement.executeQuery( "SELECT CONVERT (varchar(256), DATABASEPROPERTYEX(DB_NAME(),'collation'))" ) ) {
rs.next();
originalDBCollation = rs.getString( 1 );
}
}
catch (SQLException e) {
log.debug( e.getMessage() );
}
try ( Connection connection = serviceRegistry.getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess().obtainConnection();
Statement statement = connection.createStatement() ) {
connection.setAutoCommit( true );
statement.executeUpdate( "ALTER DATABASE CURRENT COLLATE " + changedDBCollation );
collationChanged = true;
}
}
catch ( SQLException e ) {
throw new RuntimeException( e );
}
finally {
serviceRegistry.destroy();
}
super.buildSessionFactory();
}
@Test
public void testTemporaryTableCreateWithoutCollationConflict() {
// without fixing "HHH-3326", the following exception will be thrown:
// Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "SQL_Latin1_General_CP437_BIN" in the equal to operation.
doInHibernate( this::sessionFactory, session -> {
session.createQuery( "update Woman w set w.description = :description where w.age > :age" )
.setParameter( "description", "your are old" )
.setParameter( "age", 30 )
.executeUpdate();
} );
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Human.class,
Woman.class
};
}
@Override
protected boolean rebuildSessionFactoryOnError() {
return false;
}
@Entity(name = "Human")
@Table(name = "Human")
@Inheritance(strategy = InheritanceType.JOINED)
public static abstract class Human {
@Id
String id;
int age;
}
@Entity(name = "Woman")
@Table(name = "Woman")
public static class Woman extends Human {
String description;
}
}