HHH-9491 - Add support to opt columnDefinitions out of globally-quoted-identifiers

This commit is contained in:
Steve Ebersole 2016-01-25 22:16:50 -06:00
parent bb109139e8
commit 4e36781b42
8 changed files with 224 additions and 6 deletions

View File

@ -93,6 +93,16 @@ public abstract class ObjectNameNormalizer {
return logicalName;
}
/**
* Intended only for use in handling quoting requirements for {@code column-definition}
* as defined by {@link javax.persistence.Column#columnDefinition()},
* {@link javax.persistence.JoinColumn#columnDefinition}, etc. This method should not
* be called in any other scenario.
*
* @param text The specified column definition
*
* @return The name with global quoting applied
*/
public String applyGlobalQuoting(String text) {
return database().getJdbcEnvironment().getIdentifierHelper().applyGlobalQuoting( text )
.render( database().getDialect() );

View File

@ -784,10 +784,22 @@ public interface AvailableSettings {
String DEFAULT_ENTITY_MODE = "hibernate.default_entity_mode";
/**
* Should all database identifiers be quoted.
* Should all database identifiers be quoted. A {@code true}/{@code false} option.
*/
String GLOBALLY_QUOTED_IDENTIFIERS = "hibernate.globally_quoted_identifiers";
/**
* Assuming {@link #GLOBALLY_QUOTED_IDENTIFIERS}, this allows such global quoting
* to skip column-definitions as defined by {@link javax.persistence.Column#columnDefinition()},
* {@link javax.persistence.JoinColumn#columnDefinition}, etc.
* <p/>
* JPA states that column-definitions are subject to global quoting, so by default this setting
* is {@code false} for JPA compliance. Set to {@code true} to avoid column-definitions
* being quoted due to global quoting (they will still be quoted if explicitly quoted in the
* annotation/xml).
*/
String GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS = "hibernate.globally_quoted_identifiers_skip_column_definitions";
/**
* Enable nullability checking.
* Raises an exception if a property marked as not-null is null.

View File

@ -299,7 +299,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
else {
setImplicit( false );
if ( !BinderHelper.isEmptyAnnotationValue( annJoin.columnDefinition() ) ) {
setSqlType( annJoin.columnDefinition() );
setSqlType( getBuildingContext().getObjectNameNormalizer().applyGlobalQuoting( annJoin.columnDefinition() ) );
}
if ( !BinderHelper.isEmptyAnnotationValue( annJoin.name() ) ) {
setLogicalColumnName( annJoin.name() );

View File

@ -78,6 +78,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) );
identifierHelperBuilder.setSkipGlobalQuotingForColumnDefinitions( globalQuotingSkippedForColumnDefinitions( cfgService ) );
identifierHelperBuilder.setAutoQuoteKeywords( autoKeywordQuoting( cfgService ) );
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
@ -114,6 +115,15 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
);
}
private boolean globalQuotingSkippedForColumnDefinitions(ConfigurationService cfgService) {
return cfgService.getSetting(
AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS,
StandardConverters.BOOLEAN,
// default is true for JPA compliance - DO NOT CHANGE!
true
);
}
private static boolean autoKeywordQuoting(ConfigurationService cfgService) {
return cfgService.getSetting(
AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED,
@ -218,6 +228,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) );
identifierHelperBuilder.setSkipGlobalQuotingForColumnDefinitions( globalQuotingSkippedForColumnDefinitions( cfgService ) );
identifierHelperBuilder.setAutoQuoteKeywords( autoKeywordQuoting( cfgService ) );
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
IdentifierHelper identifierHelper = null;

View File

@ -29,6 +29,7 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
private final NameQualifierSupport nameQualifierSupport;
private final boolean globallyQuoteIdentifiers;
private final boolean globallyQuoteIdentifiersSkipColumnDefinitions;
private final boolean autoQuoteKeywords;
private final Set<String> reservedWords = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER );
private final IdentifierCaseStrategy unquotedCaseStrategy;
@ -38,6 +39,7 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
JdbcEnvironment jdbcEnvironment,
NameQualifierSupport nameQualifierSupport,
boolean globallyQuoteIdentifiers,
boolean globallyQuoteIdentifiersSkipColumnDefinitions,
boolean autoQuoteKeywords,
Set<String> reservedWords,
IdentifierCaseStrategy unquotedCaseStrategy,
@ -45,6 +47,7 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
this.jdbcEnvironment = jdbcEnvironment;
this.nameQualifierSupport = nameQualifierSupport;
this.globallyQuoteIdentifiers = globallyQuoteIdentifiers;
this.globallyQuoteIdentifiersSkipColumnDefinitions = globallyQuoteIdentifiersSkipColumnDefinitions;
this.autoQuoteKeywords = autoQuoteKeywords;
if ( reservedWords != null ) {
this.reservedWords.addAll( reservedWords );
@ -90,7 +93,7 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
@Override
public Identifier applyGlobalQuoting(String text) {
return Identifier.toIdentifier( text, globallyQuoteIdentifiers );
return Identifier.toIdentifier( text, globallyQuoteIdentifiers && globallyQuoteIdentifiersSkipColumnDefinitions );
}
@Override

View File

@ -7,6 +7,7 @@
package org.hibernate.engine.jdbc.env.spi;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.cfg.AvailableSettings;
/**
* Helper for handling {@link Identifier} instances.
@ -53,13 +54,22 @@ public interface IdentifierHelper {
Identifier toIdentifier(String text, boolean quoted);
/**
* Needed to account for certain fields ({@link javax.persistence.Column#columnDefinition()} comes to mind)
* that need to be quoted id global identifier quoting is requested, but only for spec compliance. TBH, I can
* not think of a argument why column-definitions should ever be *globally* quoted, but the spec is the spec.
* Intended only for use in handling quoting requirements for {@code column-definition}
* as defined by {@link javax.persistence.Column#columnDefinition()},
* {@link javax.persistence.JoinColumn#columnDefinition}, etc. This method should not
* be called in any other scenario.
* <p/>
* This method is needed to account for that fact that the JPA spec says that {@code column-definition}
* should be quoted of global-identifier-quoting is requested. Again, this is needed for spec
* compliance. TBH, I can not think of a argument why column-definitions should ever be *globally* quoted,
* but the spec is the spec. In fact the default implementation allows applications to opt-out of
* global-identifier-quoting affecting column-definitions.
*
* @param text The text to be (possibly) quoted
*
* @return The identifier form
*
* @see AvailableSettings#GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS
*/
Identifier applyGlobalQuoting(String text);

View File

@ -34,6 +34,7 @@ public class IdentifierHelperBuilder {
private Set<String> reservedWords = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER );
private boolean globallyQuoteIdentifiers = false;
private boolean skipGlobalQuotingForColumnDefinitions = false;
private boolean autoQuoteKeywords = true;
private IdentifierCaseStrategy unquotedCaseStrategy = IdentifierCaseStrategy.MIXED;
private IdentifierCaseStrategy quotedCaseStrategy = IdentifierCaseStrategy.MIXED;
@ -136,6 +137,14 @@ public class IdentifierHelperBuilder {
this.globallyQuoteIdentifiers = globallyQuoteIdentifiers;
}
public boolean isSkipGlobalQuotingForColumnDefinitions() {
return skipGlobalQuotingForColumnDefinitions;
}
public void setSkipGlobalQuotingForColumnDefinitions(boolean skipGlobalQuotingForColumnDefinitions) {
this.skipGlobalQuotingForColumnDefinitions = skipGlobalQuotingForColumnDefinitions;
}
public void setAutoQuoteKeywords(boolean autoQuoteKeywords) {
this.autoQuoteKeywords = autoQuoteKeywords;
}
@ -191,6 +200,7 @@ public class IdentifierHelperBuilder {
jdbcEnvironment,
nameQualifierSupport,
globallyQuoteIdentifiers,
skipGlobalQuotingForColumnDefinitions,
autoQuoteKeywords,
reservedWords,
unquotedCaseStrategy,

View File

@ -0,0 +1,162 @@
/*
* 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.quote;
import java.util.Iterator;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
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.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class ColumnDefinitionQuotingTest extends BaseUnitTestCase {
@Test
@TestForIssue( jiraKey = "HHH-9491" )
public void testExplicitQuoting() {
withStandardServiceRegistry(
false,
false,
new TestWork() {
@Override
public void doTestWork(StandardServiceRegistry ssr) {
MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr )
.addAnnotatedClass( E1.class )
.buildMetadata();
metadata.validate();
PersistentClass entityBinding = metadata.getEntityBinding( E1.class.getName() );
org.hibernate.mapping.Column idColumn = extractColumn( entityBinding.getIdentifier().getColumnIterator() );
assertTrue( isQuoted( idColumn.getSqlType(), ssr ) );
org.hibernate.mapping.Column otherColumn = extractColumn( entityBinding.getProperty( "other" ).getColumnIterator() );
assertTrue( isQuoted( otherColumn.getSqlType(), ssr ) );
}
}
);
}
private org.hibernate.mapping.Column extractColumn(Iterator columnIterator) {
return (org.hibernate.mapping.Column) columnIterator.next();
}
private boolean isQuoted(String sqlType, StandardServiceRegistry ssr) {
final Dialect dialect = ssr.getService( JdbcEnvironment.class ).getDialect();
return sqlType.charAt( 0 ) == dialect.openQuote()
&& sqlType.charAt( sqlType.length()-1 ) == dialect.closeQuote();
}
@Test
@TestForIssue( jiraKey = "HHH-9491" )
public void testGlobalQuotingOptIn() {
withStandardServiceRegistry(
true,
true,
new TestWork() {
@Override
public void doTestWork(StandardServiceRegistry ssr) {
MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr )
.addAnnotatedClass( E2.class )
.buildMetadata();
metadata.validate();
PersistentClass entityBinding = metadata.getEntityBinding( E2.class.getName() );
org.hibernate.mapping.Column idColumn = extractColumn( entityBinding.getIdentifier().getColumnIterator() );
assertTrue( isQuoted( idColumn.getSqlType(), ssr ) );
org.hibernate.mapping.Column otherColumn = extractColumn( entityBinding.getProperty( "other" ).getColumnIterator() );
assertTrue( isQuoted( otherColumn.getSqlType(), ssr ) );
}
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-9491" )
public void testGlobalQuotingOptOut() {
withStandardServiceRegistry(
true,
false,
new TestWork() {
@Override
public void doTestWork(StandardServiceRegistry ssr) {
MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr )
.addAnnotatedClass( E2.class )
.buildMetadata();
metadata.validate();
PersistentClass entityBinding = metadata.getEntityBinding( E2.class.getName() );
org.hibernate.mapping.Column idColumn = extractColumn( entityBinding.getIdentifier().getColumnIterator() );
assertTrue( !isQuoted( idColumn.getSqlType(), ssr ) );
org.hibernate.mapping.Column otherColumn = extractColumn( entityBinding.getProperty( "other" ).getColumnIterator() );
assertTrue( !isQuoted( otherColumn.getSqlType(), ssr ) );
}
}
);
}
interface TestWork {
void doTestWork(StandardServiceRegistry ssr);
}
void withStandardServiceRegistry(boolean globalQuoting, boolean skipColumnDefinitions, TestWork work) {
final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
.applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, globalQuoting )
.applySetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, skipColumnDefinitions )
.build();
try {
work.doTestWork( ssr );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity
public static class E1 {
@Id
@Column( columnDefinition = "`explicitly quoted`" )
private Integer id;
@ManyToOne
@JoinColumn( columnDefinition = "`explicitly quoted`" )
private E1 other;
}
@Entity
public static class E2 {
@Id
@Column( columnDefinition = "not explicitly quoted" )
private Integer id;
@ManyToOne
@JoinColumn( columnDefinition = "not explicitly quoted" )
private E2 other;
}
}