From c0f0a731d4deba018e3d26e1bee5af51f5ff544c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 10 Sep 2018 11:05:56 +0200 Subject: [PATCH] HHH-12939 - Database name not quoted at schema update (cherry picked from commit 6e9c1893a12a5e29fcaed9263a93728bca166b31) --- gradle/libraries.gradle | 2 +- .../java/org/hibernate/mapping/Table.java | 32 +-- .../internal/AbstractSchemaMigrator.java | 16 +- .../AbstractAlterTableQuoteSchemaTest.java | 45 ++++ .../AlterTableQuoteDefaultSchemaTest.java | 194 ++++++++++++++++++ .../AlterTableQuoteSpecifiedSchemaTest.java | 166 +++++++++++++++ .../SqlServerQuoteSchemaTest.java | 2 +- 7 files changed, 420 insertions(+), 37 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 35bb364d18..589489af2e 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -111,7 +111,7 @@ ext { mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', oracle: 'com.oracle.jdbc:ojdbc8:12.2.0.1', - mssql: 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8', + mssql: 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8', db2: 'com.ibm.db2:db2jcc:10.5', hana: 'com.sap.cloud.db.jdbc:ngdbc:2.2.16', // for HANA 1 the minimum required client version is 1.120.20 diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index f7ade4cb7c..f9a9d868fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -408,7 +408,7 @@ public class Table implements RelationalModel, Serializable, Exportable { && Identifier.areEqual( schema, table.schema ) && Identifier.areEqual( catalog, table.catalog ); } - + public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) { Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { @@ -441,28 +441,16 @@ public class Table implements RelationalModel, Serializable, Exportable { Dialect dialect, Metadata metadata, TableInformation tableInfo, - String defaultCatalog, - String defaultSchema) throws HibernateException { - + Identifier defaultCatalog, + Identifier defaultSchema) throws HibernateException { + final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); - Identifier quotedCatalog = catalog != null && catalog.isQuoted() ? - new Identifier( tableInfo.getName().getCatalogName().getText(), true ) : - tableInfo.getName().getCatalogName(); - - Identifier quotedSchema = schema != null && schema.isQuoted() ? - new Identifier( tableInfo.getName().getSchemaName().getText(), true ) : - tableInfo.getName().getSchemaName(); - - Identifier quotedTable = name != null && name.isQuoted() ? - new Identifier( tableInfo.getName().getObjectName().getText(), true ) : - tableInfo.getName().getObjectName(); - final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( new QualifiedTableName( - quotedCatalog, - quotedSchema, - quotedTable + catalog != null ? catalog : defaultCatalog, + schema != null ? schema : defaultSchema, + name ), dialect ); @@ -473,7 +461,7 @@ public class Table implements RelationalModel, Serializable, Exportable { Iterator iter = getColumnIterator(); List results = new ArrayList(); - + while ( iter.hasNext() ) { final Column column = (Column) iter.next(); final ColumnInformation columnInfo = tableInfo.getColumn( Identifier.toIdentifier( column.getName(), column.isQuoted() ) ); @@ -581,7 +569,7 @@ public class Table implements RelationalModel, Serializable, Exportable { } } - + if ( col.isUnique() ) { String keyName = Constraint.generateName( "UK_", this, col ); UniqueKey uk = getOrCreateUniqueKey( keyName ); @@ -589,7 +577,7 @@ public class Table implements RelationalModel, Serializable, Exportable { buf.append( dialect.getUniqueDelegate() .getColumnDefinitionUniquenessFragment( col ) ); } - + if ( col.hasCheckConstraint() && dialect.supportsColumnCheck() ) { buf.append( " check (" ) .append( col.getCheckConstraint() ) diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java index a052702930..84769b2ab4 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java @@ -300,8 +300,8 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator { dialect, metadata, tableInformation, - getDefaultCatalogName( database, dialect ), - getDefaultSchemaName( database, dialect ) + database.getDefaultNamespace().getPhysicalName().getCatalog(), + database.getDefaultNamespace().getPhysicalName().getSchema() ), formatter, options, @@ -446,7 +446,7 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator { /** * Check if the ForeignKey already exists. First check based on definition and if that is not matched check if a key * with the exact same name exists. Keys with the same name are presumed to be functional equal. - * + * * @param foreignKey - ForeignKey, new key to be created * @param tableInformation - TableInformation, information of existing keys * @return boolean, true if key already exists @@ -581,14 +581,4 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator { } } } - - private String getDefaultCatalogName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getCatalog(); - return identifier == null ? null : identifier.render( dialect ); - } - - private String getDefaultSchemaName(Database database, Dialect dialect) { - final Identifier identifier = database.getDefaultNamespace().getPhysicalName().getSchema(); - return identifier == null ? null : identifier.render( dialect ); - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java new file mode 100644 index 0000000000..7d5ab1c526 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AbstractAlterTableQuoteSchemaTest.java @@ -0,0 +1,45 @@ +/* + * 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 . + */ +package org.hibernate.test.schemaupdate; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SQLServerDialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-12939") +public abstract class AbstractAlterTableQuoteSchemaTest extends BaseCoreFunctionalTestCase { + + private Dialect dialect = Dialect.getDialect(); + + protected String quote(String element) { + return dialect.quote( "`" + element + "`" ); + } + + protected String quote(String schema, String table) { + return quote( schema ) + "." + quote( table ); + } + + protected String regexpQuote(String element) { + return dialect.quote( "`" + element + "`" ) + .replace( "-", "\\-" ) + .replace( "[", "\\[" ) + .replace( "]", "\\]" ); + } + + protected String regexpQuote(String schema, String table) { + return regexpQuote( schema ) + "\\." + regexpQuote( table ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java new file mode 100644 index 0000000000..d40da45b2c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java @@ -0,0 +1,194 @@ +/* + * 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 . + */ +package org.hibernate.test.schemaupdate; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; +import org.junit.Test; + +/** + * @author Guillaume Smet + */ +@TestForIssue(jiraKey = "HHH-12939") +@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +public class AlterTableQuoteDefaultSchemaTest extends AbstractAlterTableQuoteSchemaTest { + + @Override + protected void afterSessionFactoryBuilt() { + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP TABLE " + quote( "default-schema", "my_entity" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "default-schema" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "CREATE SCHEMA " + quote( "default-schema" ) ) + .executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() { + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "default-schema" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + } + + @Test + public void testDefaultSchema() throws IOException { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + + try { + final MetadataSources metadataSources = new MetadataSources( ssr ) { + @Override + public MetadataBuilder getMetadataBuilder() { + MetadataBuilder metadataBuilder = super.getMetadataBuilder(); + metadataBuilder.applyImplicitSchemaName( "default-schema" ); + return metadataBuilder; + } + }; + metadataSources.addAnnotatedClass( MyEntity.class ); + + final MetadataImplementor metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + + + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + + Pattern fileContentPattern = Pattern + .compile( "create table " + regexpQuote( "default-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + + ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + try { + final MetadataSources metadataSources = new MetadataSources( ssr ) { + @Override + public MetadataBuilder getMetadataBuilder() { + MetadataBuilder metadataBuilder = super.getMetadataBuilder(); + metadataBuilder.applyImplicitSchemaName( "default-schema" ); + return metadataBuilder; + } + }; + metadataSources.addAnnotatedClass( MyEntityUpdated.class ); + + final MetadataImplementor metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + Pattern fileContentPattern = Pattern + .compile( "alter table.* " + regexpQuote( "default-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity") + public static class MyEntity { + + @Id + public Integer id; + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity") + public static class MyEntityUpdated { + + @Id + public Integer id; + + private String title; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java new file mode 100644 index 0000000000..ec957f5884 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java @@ -0,0 +1,166 @@ +/* + * 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 . + */ +package org.hibernate.test.schemaupdate; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Guillaume Smet + */ +@TestForIssue(jiraKey = "HHH-12939") +@RequiresDialectFeature(DialectChecks.SupportSchemaCreation.class) +public class AlterTableQuoteSpecifiedSchemaTest extends AbstractAlterTableQuoteSchemaTest { + + @Override + protected void afterSessionFactoryBuilt() { + + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP TABLE " + quote( "my-schema", "my_entity" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "my-schema" ) ) + .executeUpdate(); + } ); + } + catch (Exception ignore) { + } + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "CREATE SCHEMA " + quote( "my-schema" ) ) + .executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() { + try { + doInHibernate( this::sessionFactory, session -> { + session.createNativeQuery( "DROP SCHEMA " + quote( "my-schema" ) ) + .executeUpdate(); + } ); + + } + catch (Exception ignore) { + } + } + + @Test + public void testSpecifiedSchema() throws IOException { + File output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + + try { + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( MyEntity.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + Pattern fileContentPattern = Pattern.compile( "create table " + regexpQuote( "my-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + + ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, Boolean.TRUE.toString() ) + .build(); + try { + final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) + .addAnnotatedClass( MyEntityUpdated.class ) + .buildMetadata(); + metadata.validate(); + + new SchemaUpdate() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setDelimiter( ";" ) + .setFormat( true ) + .execute( EnumSet.of( TargetType.DATABASE, TargetType.SCRIPT ), metadata ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + + try { + String fileContent = new String( Files.readAllBytes( output.toPath() ) ); + Pattern fileContentPattern = Pattern.compile( "alter table.* " + regexpQuote( "my-schema", "my_entity" ) ); + Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); + assertThat( fileContentMatcher.find(), is( true ) ); + } + catch (IOException e) { + fail( e.getMessage() ); + } + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity", schema = "my-schema") + public static class MyEntity { + + @Id + public Integer id; + } + + @Entity(name = "MyEntity") + @Table(name = "my_entity", schema = "my-schema") + public static class MyEntityUpdated { + + @Id + public Integer id; + + private String title; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java index f6cade36da..af70650e95 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SqlServerQuoteSchemaTest.java @@ -151,7 +151,7 @@ public class SqlServerQuoteSchemaTest extends BaseCoreFunctionalTestCase { try { String fileContent = new String( Files.readAllBytes( output.toPath() ) ); - Pattern fileContentPattern = Pattern.compile( "alter table .*?\\.\\[my\\-schema\\]\\.\\[my_entity\\]" ); + Pattern fileContentPattern = Pattern.compile( "alter table \\[my\\-schema\\]\\.\\[my_entity\\]" ); Matcher fileContentMatcher = fileContentPattern.matcher( fileContent.toLowerCase() ); assertThat( fileContentMatcher.find(), is( true ) ); }