HHH-9820 - Handle JDBC drivers that do not properly report metadata regarding case of identifiers

This commit is contained in:
Steve Ebersole 2015-05-27 11:28:57 -05:00
parent d58ef6950c
commit a51f300253
12 changed files with 499 additions and 216 deletions

View File

@ -11,6 +11,7 @@ import java.io.OutputStream;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -53,6 +54,8 @@ import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.env.internal.DefaultSchemaNameResolver;
import org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver;
import org.hibernate.engine.jdbc.spi.JdbcServices;
@ -1846,7 +1849,8 @@ public abstract class Dialect implements ConversionContext {
}
/**
* @deprecated See {@link #determineKeywordsForAutoQuoting} instead
* @deprecated These are only ever used (if at all) from the code that handles identifier quoting. So
* see {@link #buildIdentifierHelper} instead
*/
@Deprecated
public Set<String> getKeywords() {
@ -1854,29 +1858,46 @@ public abstract class Dialect implements ConversionContext {
}
/**
* Hook into auto-quoting of identifiers that are deemed to be keywords. By default we
* return all of the following here:<ul>
* <li>all keywords as defined by ANSI SQL:2003 specification</li>
* <li>all "extra" keywords reported by the JDBC driver </li>
* <li>all Dialect-registered "extra" keywords</li>
* Build the IdentifierHelper indicated by this Dialect for handling identifier conversions.
* Returning {@code null} is allowed and indicates that Hibernate should fallback to building a
* "standard" helper. In the fallback path, any changes made to the IdentifierHelperBuilder
* during this call will still be incorporated into the built IdentifierHelper.
* <p/>
* The incoming builder will have the following set:<ul>
* <li>{@link IdentifierHelperBuilder#isGloballyQuoteIdentifiers()}</li>
* <li>{@link IdentifierHelperBuilder#getUnquotedCaseStrategy()} - initialized to UPPER</li>
* <li>{@link IdentifierHelperBuilder#getQuotedCaseStrategy()} - initialized to MIXED</li>
* </ul>
* <p/>
* Subclasses are free to override this as they see fit. Just be aware that overriding this
* does affect what identifiers are auto-quoted based on being seen as a keyword.
* <p/>
* NOTE: The code that ultimately consumes these and uses them stores them in a case-insensitive
* Set, so case here does not matter.
* By default Hibernate will do the following:<ul>
* <li>Call {@link IdentifierHelperBuilder#applyIdentifierCasing(DatabaseMetaData)}
* <li>Call {@link IdentifierHelperBuilder#applyReservedWords(DatabaseMetaData)}
* <li>Applies {@link AnsiSqlKeywords#sql2003()} as reserved words</li>
* <li>Applies the {#link #sqlKeywords} collected here as reserved words</li>
* <li>Applies the Dialect's NameQualifierSupport, if it defines one</li>
* </ul>
*
* @see org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords#sql2003()
* @see java.sql.DatabaseMetaData#getSQLKeywords()
* @see #registerKeyword
* @param builder A semi-configured IdentifierHelper builder.
* @param dbMetaData Access to the metadata returned from the driver if needed and if available. WARNING: may be {@code null}
*
* @return The IdentifierHelper instance to use, or {@code null} to indicate Hibernate should use its fallback path
*
* @throws SQLException Accessing the DatabaseMetaData can throw it. Just re-throw and Hibernate will handle.
*
* @see #getNameQualifierSupport()
*/
public Set<String> determineKeywordsForAutoQuoting(Set<String> databaseMetadataReportedKeywords) {
final Set<String> keywords = new HashSet<String>();
keywords.addAll( AnsiSqlKeywords.INSTANCE.sql2003() );
keywords.addAll( databaseMetadataReportedKeywords );
keywords.addAll( sqlKeywords );
return keywords;
public IdentifierHelper buildIdentifierHelper(
IdentifierHelperBuilder builder,
DatabaseMetaData dbMetaData) throws SQLException {
builder.applyIdentifierCasing( dbMetaData );
builder.applyReservedWords( dbMetaData );
builder.applyReservedWords( AnsiSqlKeywords.INSTANCE.sql2003() );
builder.applyReservedWords( sqlKeywords );
builder.setNameQualifierSupport( getNameQualifierSupport() );
return builder.build();
}

View File

@ -9,11 +9,9 @@ package org.hibernate.engine.jdbc.env.internal;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
@ -22,7 +20,9 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.LobCreatorBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
@ -36,10 +36,14 @@ import org.hibernate.exception.internal.StandardSQLExceptionConverter;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class JdbcEnvironmentImpl implements JdbcEnvironment {
private static final Logger log = Logger.getLogger( JdbcEnvironmentImpl.class );
private final Dialect dialect;
private final SqlExceptionHelper sqlExceptionHelper;
@ -51,7 +55,6 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
private final LobCreatorBuilderImpl lobCreatorBuilder;
private final LinkedHashSet<TypeInfo> typeInfoSet = new LinkedHashSet<TypeInfo>();
private final Set<String> reservedWords = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER );
/**
* Constructor form used when the JDBC {@link java.sql.DatabaseMetaData} is not available.
@ -62,6 +65,8 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
public JdbcEnvironmentImpl(ServiceRegistryImplementor serviceRegistry, Dialect dialect) {
this.dialect = dialect;
final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class );
NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport();
if ( nameQualifierSupport == null ) {
// assume both catalogs and schemas are supported
@ -71,39 +76,43 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
this.sqlExceptionHelper = buildSqlExceptionHelper( dialect );
this.extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl.Builder( this ).build();
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( Collections.<String>emptySet() ) );
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) );
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
final boolean globallyQuoteIdentifiers = serviceRegistry.getService( ConfigurationService.class )
.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false );
// a simple impl that works on H2
this.identifierHelper = new NormalizingIdentifierHelperImpl(
this,
nameQualifierSupport,
globallyQuoteIdentifiers,
true, // storesMixedCaseQuotedIdentifiers
false, // storesLowerCaseQuotedIdentifiers
false, // storesUpperCaseQuotedIdentifiers
false, // storesMixedCaseIdentifiers
true, // storesUpperCaseIdentifiers
false // storesLowerCaseIdentifiers
);
IdentifierHelper identifierHelper = null;
try {
identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, null );
}
catch (SQLException sqle) {
// should never ever happen
log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle );
}
if ( identifierHelper == null ) {
identifierHelper = identifierHelperBuilder.build();
}
this.identifierHelper = identifierHelper;
this.currentCatalog = identifierHelper.toIdentifier(
serviceRegistry.getService( ConfigurationService.class )
.getSetting( AvailableSettings.DEFAULT_CATALOG, StandardConverters.STRING )
cfgService.getSetting( AvailableSettings.DEFAULT_CATALOG, StandardConverters.STRING )
);
this.currentSchema = Identifier.toIdentifier(
serviceRegistry.getService( ConfigurationService.class )
.getSetting( AvailableSettings.DEFAULT_SCHEMA, StandardConverters.STRING )
cfgService.getSetting( AvailableSettings.DEFAULT_SCHEMA, StandardConverters.STRING )
);
// again, a simple impl that works on H2
this.qualifiedObjectNameFormatter = new QualifiedObjectNameFormatterStandardImpl( nameQualifierSupport );
this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder();
}
private static boolean globalQuoting(ConfigurationService cfgService) {
return cfgService.getSetting(
AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS,
StandardConverters.BOOLEAN,
false
);
}
/**
* Constructor form used from testing
*
@ -123,22 +132,20 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
nameQualifierSupport = determineNameQualifierSupport( databaseMetaData );
}
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( extractedMetaDataSupport.getExtraKeywords() ) );
final boolean globallyQuoteIdentifiers = false;
// a simple impl that works on H2
this.identifierHelper = new NormalizingIdentifierHelperImpl(
this,
nameQualifierSupport,
globallyQuoteIdentifiers,
true, // storesMixedCaseQuotedIdentifiers
false, // storesLowerCaseQuotedIdentifiers
false, // storesUpperCaseQuotedIdentifiers
false, // storesMixedCaseIdentifiers
true, // storesUpperCaseIdentifiers
false // storesLowerCaseIdentifiers
);
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
IdentifierHelper identifierHelper = null;
try {
identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData );
}
catch (SQLException sqle) {
// should never ever happen
log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle );
}
if ( identifierHelper == null ) {
identifierHelper = identifierHelperBuilder.build();
}
this.identifierHelper = identifierHelper;
this.currentCatalog = null;
this.currentSchema = null;
@ -184,6 +191,8 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
DatabaseMetaData databaseMetaData) throws SQLException {
this.dialect = dialect;
final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class );
this.sqlExceptionHelper = buildSqlExceptionHelper( dialect );
this.extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl.Builder( this )
@ -196,22 +205,21 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
nameQualifierSupport = determineNameQualifierSupport( databaseMetaData );
}
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( extractedMetaDataSupport.getExtraKeywords() ) );
final boolean globallyQuoteIdentifiers = serviceRegistry.getService( ConfigurationService.class )
.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false );
this.identifierHelper = new NormalizingIdentifierHelperImpl(
this,
nameQualifierSupport,
globallyQuoteIdentifiers,
databaseMetaData.storesMixedCaseQuotedIdentifiers(),
databaseMetaData.storesLowerCaseQuotedIdentifiers(),
databaseMetaData.storesUpperCaseQuotedIdentifiers(),
databaseMetaData.storesMixedCaseIdentifiers(),
databaseMetaData.storesUpperCaseIdentifiers(),
databaseMetaData.storesLowerCaseIdentifiers()
);
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) );
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
IdentifierHelper identifierHelper = null;
try {
identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData );
}
catch (SQLException sqle) {
// should never ever happen
log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle );
}
if ( identifierHelper == null ) {
identifierHelper = identifierHelperBuilder.build();
}
this.identifierHelper = identifierHelper;
// and that current-catalog and current-schema happen after it
this.currentCatalog = identifierHelper.toIdentifier( extractedMetaDataSupport.getConnectionCatalogName() );
@ -225,7 +233,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
this.typeInfoSet.addAll( TypeInfo.extractTypeInfo( databaseMetaData ) );
this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder(
serviceRegistry.getService( ConfigurationService.class ).getSettings(),
cfgService.getSettings(),
databaseMetaData.getConnection()
);
}
@ -307,11 +315,6 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
return identifierHelper;
}
@Override
public boolean isReservedWord(String word) {
return reservedWords.contains( word );
}
@Override
public SqlExceptionHelper getSqlExceptionHelper() {
return sqlExceptionHelper;

View File

@ -7,8 +7,11 @@
package org.hibernate.engine.jdbc.env.internal;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
@ -25,59 +28,31 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
private final NameQualifierSupport nameQualifierSupport;
private final boolean globallyQuoteIdentifiers;
private final boolean storesMixedCaseQuotedIdentifiers;
private final boolean storesLowerCaseQuotedIdentifiers;
private final boolean storesUpperCaseQuotedIdentifiers;
private final boolean storesMixedCaseIdentifiers;
private final boolean storesUpperCaseIdentifiers;
private final boolean storesLowerCaseIdentifiers;
private final Set<String> reservedWords = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER );
private final IdentifierCaseStrategy unquotedCaseStrategy;
private final IdentifierCaseStrategy quotedCaseStrategy;
public NormalizingIdentifierHelperImpl(
JdbcEnvironment jdbcEnvironment,
NameQualifierSupport nameQualifierSupport,
boolean globallyQuoteIdentifiers,
boolean storesMixedCaseQuotedIdentifiers,
boolean storesLowerCaseQuotedIdentifiers,
boolean storesUpperCaseQuotedIdentifiers,
boolean storesMixedCaseIdentifiers,
boolean storesUpperCaseIdentifiers,
boolean storesLowerCaseIdentifiers) {
Set<String> reservedWords,
IdentifierCaseStrategy unquotedCaseStrategy,
IdentifierCaseStrategy quotedCaseStrategy) {
this.jdbcEnvironment = jdbcEnvironment;
this.nameQualifierSupport = nameQualifierSupport;
this.globallyQuoteIdentifiers = globallyQuoteIdentifiers;
this.storesMixedCaseQuotedIdentifiers = storesMixedCaseQuotedIdentifiers;
this.storesLowerCaseQuotedIdentifiers = storesLowerCaseQuotedIdentifiers;
this.storesUpperCaseQuotedIdentifiers = storesUpperCaseQuotedIdentifiers;
this.storesMixedCaseIdentifiers = storesMixedCaseIdentifiers;
this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers;
this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers;
if ( storesMixedCaseQuotedIdentifiers && storesLowerCaseQuotedIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in mixed, upper and lower case" );
}
else if ( storesMixedCaseQuotedIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in both mixed and upper case" );
}
else if ( storesMixedCaseQuotedIdentifiers && storesLowerCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in both mixed and lower case" );
}
if ( storesUpperCaseIdentifiers && storesLowerCaseIdentifiers ) {
log.warn( "JDBC Driver reports it stores non-quoted identifiers in both upper and lower case" );
}
if ( storesUpperCaseIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores both quoted and non-quoted identifiers in upper case" );
}
if ( storesLowerCaseIdentifiers && storesLowerCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores both quoted and non-quoted identifiers in lower case" );
if ( reservedWords != null ) {
this.reservedWords.addAll( reservedWords );
}
this.unquotedCaseStrategy = unquotedCaseStrategy == null ? IdentifierCaseStrategy.UPPER : unquotedCaseStrategy;
this.quotedCaseStrategy = quotedCaseStrategy == null ? IdentifierCaseStrategy.MIXED : quotedCaseStrategy;
}
@Override
public Identifier normalizeQuoting(Identifier identifier) {
log.tracef( "Normalizing identifier quoting [%s]", identifier );
if ( identifier == null ) {
return null;
}
@ -87,10 +62,12 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
}
if ( globallyQuoteIdentifiers ) {
log.tracef( "Forcing identifier [%s] to quoted for global quoting", identifier );
return Identifier.toIdentifier( identifier.getText(), true );
}
if ( jdbcEnvironment.isReservedWord( identifier.getText() ) ) {
if ( isReservedWord( identifier.getText() ) ) {
log.tracef( "Forcing identifier [%s] to quoted as recognized reserveed word", identifier );
return Identifier.toIdentifier( identifier.getText(), true );
}
@ -112,16 +89,18 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
return Identifier.toIdentifier( text, globallyQuoteIdentifiers );
}
// In the DatabaseMetaData method params for catalog and schema name have the following meaning:
// 1) <""> means to match things "without a catalog/schema"
// 2) <null> means to not limit results based on this field
//
// todo : not sure how "without a catalog/schema" is interpreted. Current?
@Override
public boolean isReservedWord(String word) {
return reservedWords.contains( word );
}
@Override
public String toMetaDataCatalogName(Identifier identifier) {
log.tracef( "Normalizing identifier quoting for catalog name [%s]", identifier );
if ( !nameQualifierSupport.supportsCatalogs() ) {
// null is used to tell DBMD to not limit results based on catalog.
log.trace( "Environment does not support catalogs; returning null" );
// null is used to tell DatabaseMetaData to not limit results based on catalog.
return null;
}
@ -141,39 +120,48 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
}
if ( identifier.isQuoted() ) {
if ( storesMixedCaseQuotedIdentifiers ) {
return identifier.getText();
}
else if ( storesUpperCaseQuotedIdentifiers ) {
return identifier.getText().toUpperCase( Locale.ENGLISH );
}
else if ( storesLowerCaseQuotedIdentifiers ) {
return identifier.getText().toLowerCase( Locale.ENGLISH );
}
else {
return identifier.getText();
switch ( quotedCaseStrategy ) {
case UPPER: {
log.tracef( "Rendering quoted identifier [%s] in upper case for use in DatabaseMetaData", identifier );
return identifier.getText().toUpperCase( Locale.ROOT );
}
case LOWER: {
log.tracef( "Rendering quoted identifier [%s] in lower case for use in DatabaseMetaData", identifier );
return identifier.getText().toLowerCase( Locale.ROOT );
}
default: {
// default is mixed case
log.tracef( "Rendering quoted identifier [%s] in mixed case for use in DatabaseMetaData", identifier );
return identifier.getText();
}
}
}
else {
if ( storesMixedCaseIdentifiers ) {
return identifier.getText();
}
else if ( storesUpperCaseIdentifiers ) {
return identifier.getText().toUpperCase( Locale.ENGLISH );
}
else if ( storesLowerCaseIdentifiers ) {
return identifier.getText().toLowerCase( Locale.ENGLISH );
}
else {
return identifier.getText();
switch ( unquotedCaseStrategy ) {
case MIXED: {
log.tracef( "Rendering unquoted identifier [%s] in mixed case for use in DatabaseMetaData", identifier );
return identifier.getText();
}
case LOWER: {
log.tracef( "Rendering unquoted identifier [%s] in lower case for use in DatabaseMetaData", identifier );
return identifier.getText().toLowerCase( Locale.ROOT );
}
default: {
// default is upper case
log.tracef( "Rendering unquoted identifier [%s] in upper case for use in DatabaseMetaData", identifier );
return identifier.getText().toUpperCase( Locale.ROOT );
}
}
}
}
@Override
public String toMetaDataSchemaName(Identifier identifier) {
log.tracef( "Normalizing identifier quoting for schema name [%s]", identifier );
if ( !nameQualifierSupport.supportsSchemas() ) {
// null is used to tell DBMD to not limit results based on schema.
// null is used to tell DatabaseMetaData to not limit results based on schema.
log.trace( "Environment does not support catalogs; returning null" );
return null;
}
@ -189,6 +177,8 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
@Override
public String toMetaDataObjectName(Identifier identifier) {
log.tracef( "Normalizing identifier quoting for object name [%s]", identifier );
if ( identifier == null ) {
// if this method was called, the value is needed
throw new IllegalArgumentException( "null was passed as an object name" );
@ -211,32 +201,40 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
}
public Identifier toIdentifierFromMetaData(String text) {
log.tracef( "Interpreting return value [%s] from DatabaseMetaData as identifier", text );
if ( globallyQuoteIdentifiers ) {
log.trace( "Forcing DatabaseMetaData return value as quoted due to global quoting" );
return Identifier.toIdentifier( text, true );
}
if ( isReservedWord( text ) ) {
// unequivocally it needs to be quoted...
log.trace( "Forcing DatabaseMetaData return value as quoted as it was recognized as a reserved word" );
return Identifier.toIdentifier( text, true );
}
// lovely decipher of whether the incoming value represents a quoted identifier...
final boolean isUpperCase = text.toUpperCase(Locale.ROOT).equals( text );
final boolean isLowerCase = text.toLowerCase(Locale.ROOT).equals( text );
final boolean isUpperCase = text.toUpperCase( Locale.ROOT ).equals( text );
final boolean isLowerCase = text.toLowerCase( Locale.ROOT ).equals( text );
final boolean isMixedCase = ! isLowerCase && ! isUpperCase;
if ( jdbcEnvironment.isReservedWord( text ) ) {
// unequivocally it needs to be quoted...
if ( quotedCaseStrategy == IdentifierCaseStrategy.MIXED && isMixedCase ) {
log.trace( "Interpreting return value as quoted due to case strategy" );
return Identifier.toIdentifier( text, true );
}
if ( storesMixedCaseQuotedIdentifiers && isMixedCase ) {
if ( quotedCaseStrategy == IdentifierCaseStrategy.LOWER && isLowerCase ) {
log.trace( "Interpreting return value as quoted due to case strategy" );
return Identifier.toIdentifier( text, true );
}
if ( storesLowerCaseQuotedIdentifiers && isLowerCase ) {
return Identifier.toIdentifier( text, true );
}
if ( storesUpperCaseQuotedIdentifiers && isUpperCase ) {
if ( quotedCaseStrategy == IdentifierCaseStrategy.UPPER && isUpperCase ) {
log.trace( "Interpreting return value as quoted due to case strategy" );
return Identifier.toIdentifier( text, true );
}
log.trace( "Interpreting return value as unquoted due to case strategy" );
return Identifier.toIdentifier( text );
}

View File

@ -25,21 +25,21 @@ public interface ExtractedDatabaseMetaData {
*
* @return The JDBC environment
*/
public JdbcEnvironment getJdbcEnvironment();
JdbcEnvironment getJdbcEnvironment();
/**
* Retrieve the name of the catalog in effect when we connected to the database.
*
* @return The catalog name
*/
public String getConnectionCatalogName();
String getConnectionCatalogName();
/**
* Retrieve the name of the schema in effect when we connected to the database.
*
* @return The schema name
*/
public String getConnectionSchemaName();
String getConnectionSchemaName();
/**
* Set of type info reported by the driver.
@ -48,7 +48,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#getTypeInfo()
*/
public LinkedHashSet<TypeInfo> getTypeInfoSet();
LinkedHashSet<TypeInfo> getTypeInfoSet();
/**
* Get the list of extra keywords (beyond standard SQL92 keywords) reported by the driver.
@ -57,7 +57,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#getSQLKeywords()
*/
public Set<String> getExtraKeywords();
Set<String> getExtraKeywords();
/**
* Does the driver report supporting named parameters?
@ -65,7 +65,7 @@ public interface ExtractedDatabaseMetaData {
* @return {@code true} indicates the driver reported true; {@code false} indicates the driver reported false
* or that the driver could not be asked.
*/
public boolean supportsNamedParameters();
boolean supportsNamedParameters();
/**
* Does the driver report supporting REF_CURSORs?
@ -73,7 +73,7 @@ public interface ExtractedDatabaseMetaData {
* @return {@code true} indicates the driver reported true; {@code false} indicates the driver reported false
* or that the driver could not be asked.
*/
public boolean supportsRefCursors();
boolean supportsRefCursors();
/**
* Did the driver report to supporting scrollable result sets?
@ -82,7 +82,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#supportsResultSetType
*/
public boolean supportsScrollableResults();
boolean supportsScrollableResults();
/**
* Did the driver report to supporting retrieval of generated keys?
@ -91,7 +91,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#supportsGetGeneratedKeys
*/
public boolean supportsGetGeneratedKeys();
boolean supportsGetGeneratedKeys();
/**
* Did the driver report to supporting batched updates?
@ -100,7 +100,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#supportsBatchUpdates
*/
public boolean supportsBatchUpdates();
boolean supportsBatchUpdates();
/**
* Did the driver report to support performing DDL within transactions?
@ -109,7 +109,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#dataDefinitionIgnoredInTransactions
*/
public boolean supportsDataDefinitionInTransaction();
boolean supportsDataDefinitionInTransaction();
/**
* Did the driver report to DDL statements performed within a transaction performing an implicit commit of the
@ -120,7 +120,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#dataDefinitionCausesTransactionCommit()
*/
public boolean doesDataDefinitionCauseTransactionCommit();
boolean doesDataDefinitionCauseTransactionCommit();
/**
* Retrieve the type of codes the driver says it uses for {@code SQLState}. They might follow either
@ -130,7 +130,7 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#getSQLStateType()
*/
public SQLStateType getSqlStateType();
SQLStateType getSqlStateType();
/**
* Did the driver report that updates to a LOB locator affect a copy of the LOB?
@ -139,5 +139,5 @@ public interface ExtractedDatabaseMetaData {
*
* @see java.sql.DatabaseMetaData#locatorsUpdateCopy()
*/
public boolean doesLobLocatorUpdateCopy();
boolean doesLobLocatorUpdateCopy();
}

View File

@ -0,0 +1,36 @@
/*
* 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.engine.jdbc.env.spi;
/**
* An enumeration of the way DatabaseMetaData might store and return identifiers
*
* @author Steve Ebersole
*/
public enum IdentifierCaseStrategy {
/**
* The identifier is stored in mixed case.
*
* @see java.sql.DatabaseMetaData#storesMixedCaseIdentifiers()
* @see java.sql.DatabaseMetaData#storesMixedCaseQuotedIdentifiers()
*/
MIXED,
/**
* The identifier is stored in upper case.
*
* @see java.sql.DatabaseMetaData#storesUpperCaseIdentifiers()
* @see java.sql.DatabaseMetaData#storesUpperCaseQuotedIdentifiers()
*/
UPPER,
/**
* The identifier is stored in lower case.
*
* @see java.sql.DatabaseMetaData#storesLowerCaseIdentifiers()
* @see java.sql.DatabaseMetaData#storesLowerCaseQuotedIdentifiers()
*/
LOWER
}

View File

@ -22,7 +22,7 @@ public interface IdentifierHelper {
*
* @return The quoting-normalized Identifier.
*/
public Identifier normalizeQuoting(Identifier identifier);
Identifier normalizeQuoting(Identifier identifier);
/**
* Generate an Identifier instance from its simple name as obtained from mapping
@ -35,7 +35,7 @@ public interface IdentifierHelper {
*
* @return The identifier form of the name.
*/
public Identifier toIdentifier(String text);
Identifier toIdentifier(String text);
/**
* Generate an Identifier instance from its simple name as obtained from mapping
@ -50,7 +50,7 @@ public interface IdentifierHelper {
*
* @return The identifier form of the name.
*/
public Identifier toIdentifier(String text, boolean quoted);
Identifier toIdentifier(String text, boolean quoted);
/**
* Needed to account for certain fields ({@link javax.persistence.Column#columnDefinition()} comes to mind)
@ -61,7 +61,16 @@ public interface IdentifierHelper {
*
* @return The identifier form
*/
public Identifier applyGlobalQuoting(String text);
Identifier applyGlobalQuoting(String text);
/**
* Check whether the given word represents a reserved word.
*
* @param word The word to check
*
* @return {@code true} if the given word represents a reserved word; {@code false} otherwise.
*/
boolean isReservedWord(String word);
/**
* Render the Identifier representation of a catalog name into the String form needed
@ -71,7 +80,7 @@ public interface IdentifierHelper {
*
* @return The String representation of the given catalog name
*/
public String toMetaDataCatalogName(Identifier catalogIdentifier);
String toMetaDataCatalogName(Identifier catalogIdentifier);
/**
* Render the Identifier representation of a schema name into the String form needed
@ -81,7 +90,7 @@ public interface IdentifierHelper {
*
* @return The String representation of the given schema name
*/
public String toMetaDataSchemaName(Identifier schemaIdentifier);
String toMetaDataSchemaName(Identifier schemaIdentifier);
/**
* Render the Identifier representation of an object name (table, sequence, etc) into the
@ -91,7 +100,7 @@ public interface IdentifierHelper {
*
* @return The String representation of the given object name
*/
public String toMetaDataObjectName(Identifier identifier);
String toMetaDataObjectName(Identifier identifier);
/**
* Parse an Identifier representation from the String representation of a catalog name
@ -101,7 +110,7 @@ public interface IdentifierHelper {
*
* @return The parsed Identifier representation of the given catalog name
*/
public Identifier fromMetaDataCatalogName(String catalogName);
Identifier fromMetaDataCatalogName(String catalogName);
/**
* Parse an Identifier representation from the String representation of a schema name
@ -111,7 +120,7 @@ public interface IdentifierHelper {
*
* @return The parsed Identifier representation of the given schema name
*/
public Identifier fromMetaDataSchemaName(String schemaName);
Identifier fromMetaDataSchemaName(String schemaName);
/**
* Parse an Identifier representation from the String representation of an object name
@ -121,5 +130,5 @@ public interface IdentifierHelper {
*
* @return The parsed Identifier representation of the given object name
*/
public Identifier fromMetaDataObjectName(String name);
Identifier fromMetaDataObjectName(String name);
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.engine.jdbc.env.spi;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.hibernate.engine.jdbc.env.internal.NormalizingIdentifierHelperImpl;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.jboss.logging.Logger;
/**
* Builder for IdentifierHelper instances. Mainly here to allow progressive
* building of the immutable (after instantiation) IdentifierHelper.
*
* @author Steve Ebersole
*/
public class IdentifierHelperBuilder {
private static final Logger log = Logger.getLogger( IdentifierHelperBuilder.class );
private final JdbcEnvironment jdbcEnvironment;
private NameQualifierSupport nameQualifierSupport = NameQualifierSupport.BOTH;
private Set<String> reservedWords = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER );
private boolean globallyQuoteIdentifiers = false;
private IdentifierCaseStrategy unquotedCaseStrategy = IdentifierCaseStrategy.UPPER;
private IdentifierCaseStrategy quotedCaseStrategy = IdentifierCaseStrategy.MIXED;
public static IdentifierHelperBuilder from(JdbcEnvironment jdbcEnvironment) {
return new IdentifierHelperBuilder( jdbcEnvironment );
}
private IdentifierHelperBuilder(JdbcEnvironment jdbcEnvironment) {
this.jdbcEnvironment = jdbcEnvironment;
}
/**
* Applies any reserved words reported via {@link DatabaseMetaData#getSQLKeywords()}
*
* @param metaData The metadata to get reserved words from
*
* @throws SQLException Any access to DatabaseMetaData can case SQLException; just re-throw.
*/
public void applyReservedWords(DatabaseMetaData metaData) throws SQLException {
if ( metaData == null ) {
return;
}
this.reservedWords.addAll( parseKeywords( metaData.getSQLKeywords() ) );
}
private static List<String> parseKeywords(String extraKeywordsString) {
if ( StringHelper.isEmpty( extraKeywordsString ) ) {
return Collections.emptyList();
}
return StringHelper.parseCommaSeparatedString( extraKeywordsString );
}
public void applyIdentifierCasing(DatabaseMetaData metaData) throws SQLException {
if ( metaData == null ) {
return;
}
final int unquotedAffirmatives = ArrayHelper.countTrue(
metaData.storesLowerCaseIdentifiers(),
metaData.storesUpperCaseIdentifiers(),
metaData.storesMixedCaseIdentifiers()
);
if ( unquotedAffirmatives == 0 ) {
log.debug( "JDBC driver metadata reported database stores unquoted identifiers in neither upper, lower nor mixed case" );
}
else {
// NOTE : still "dodgy" if more than one is true
if ( unquotedAffirmatives > 1 ) {
log.debug( "JDBC driver metadata reported database stores unquoted identifiers in more than one case" );
}
if ( metaData.storesUpperCaseIdentifiers() ) {
this.unquotedCaseStrategy = IdentifierCaseStrategy.UPPER;
}
else if ( metaData.storesLowerCaseIdentifiers() ) {
this.unquotedCaseStrategy = IdentifierCaseStrategy.LOWER;
}
else {
this.unquotedCaseStrategy = IdentifierCaseStrategy.MIXED;
}
}
final int quotedAffirmatives = ArrayHelper.countTrue(
metaData.storesLowerCaseQuotedIdentifiers(),
metaData.storesUpperCaseQuotedIdentifiers(),
metaData.storesMixedCaseQuotedIdentifiers()
);
if ( quotedAffirmatives == 0 ) {
log.debug( "JDBC driver metadata reported database stores quoted identifiers in neither upper, lower nor mixed case" );
}
else {
// NOTE : still "dodgy" if more than one is true
if ( quotedAffirmatives > 1 ) {
log.debug( "JDBC driver metadata reported database stores quoted identifiers in more than one case" );
}
if ( metaData.storesMixedCaseQuotedIdentifiers() ) {
this.quotedCaseStrategy = IdentifierCaseStrategy.MIXED;
}
else if ( metaData.storesLowerCaseQuotedIdentifiers() ) {
this.quotedCaseStrategy = IdentifierCaseStrategy.LOWER;
}
else {
this.quotedCaseStrategy = IdentifierCaseStrategy.UPPER;
}
}
}
public boolean isGloballyQuoteIdentifiers() {
return globallyQuoteIdentifiers;
}
public void setGloballyQuoteIdentifiers(boolean globallyQuoteIdentifiers) {
this.globallyQuoteIdentifiers = globallyQuoteIdentifiers;
}
public NameQualifierSupport getNameQualifierSupport() {
return nameQualifierSupport;
}
public void setNameQualifierSupport(NameQualifierSupport nameQualifierSupport) {
this.nameQualifierSupport = nameQualifierSupport == null ? NameQualifierSupport.BOTH : nameQualifierSupport;
}
public IdentifierCaseStrategy getUnquotedCaseStrategy() {
return unquotedCaseStrategy;
}
public void setUnquotedCaseStrategy(IdentifierCaseStrategy unquotedCaseStrategy) {
this.unquotedCaseStrategy = unquotedCaseStrategy;
}
public IdentifierCaseStrategy getQuotedCaseStrategy() {
return quotedCaseStrategy;
}
public void setQuotedCaseStrategy(IdentifierCaseStrategy quotedCaseStrategy) {
this.quotedCaseStrategy = quotedCaseStrategy;
}
public void clearReservedWords() {
this.reservedWords.clear();
}
public void applyReservedWords(Set<String> words) {
this.reservedWords.addAll( words );
}
public void setReservedWords(Set<String> words) {
clearReservedWords();
applyReservedWords( words );
}
public IdentifierHelper build() {
if ( unquotedCaseStrategy == quotedCaseStrategy ) {
log.debugf(
"IdentifierCaseStrategy for both quoted and unquoted identifiers was set " +
"to the same strategy [%s]; that will likely lead to problems in schema update " +
"and validation if using quoted identifiers",
unquotedCaseStrategy.name()
);
}
return new NormalizingIdentifierHelperImpl(
jdbcEnvironment,
nameQualifierSupport,
globallyQuoteIdentifiers,
reservedWords,
unquotedCaseStrategy,
quotedCaseStrategy
);
}
}

View File

@ -24,7 +24,7 @@ public interface JdbcEnvironment extends Service {
*
* @return The dialect.
*/
public Dialect getDialect();
Dialect getDialect();
/**
* Access to the bits of information we pulled off the JDBC {@link java.sql.DatabaseMetaData} (that did not get
@ -32,7 +32,7 @@ public interface JdbcEnvironment extends Service {
*
* @return The values extracted from JDBC DatabaseMetaData
*/
public ExtractedDatabaseMetaData getExtractedDatabaseMetaData();
ExtractedDatabaseMetaData getExtractedDatabaseMetaData();
/**
* Get the current database catalog. Typically will come from either {@link java.sql.Connection#getCatalog()}
@ -40,7 +40,7 @@ public interface JdbcEnvironment extends Service {
*
* @return The current catalog.
*/
public Identifier getCurrentCatalog();
Identifier getCurrentCatalog();
/**
* Get the current database catalog. Typically will come from either
@ -49,14 +49,14 @@ public interface JdbcEnvironment extends Service {
*
* @return The current schema
*/
public Identifier getCurrentSchema();
Identifier getCurrentSchema();
/**
* Obtain support for formatting qualified object names.
*
* @return Qualified name support.
*/
public QualifiedObjectNameFormatter getQualifiedObjectNameFormatter();
QualifiedObjectNameFormatter getQualifiedObjectNameFormatter();
/**
* Obtain the helper for dealing with identifiers in this environment.
@ -66,30 +66,21 @@ public interface JdbcEnvironment extends Service {
*
* @return The identifier helper.
*/
public IdentifierHelper getIdentifierHelper();
/**
* Check whether the given word represents a reserved word.
*
* @param word The word to check
*
* @return {@code true} if the given word represents a reserved word; {@code false} otherwise.
*/
public boolean isReservedWord(String word);
IdentifierHelper getIdentifierHelper();
/**
* Obtain the helper for dealing with JDBC {@link java.sql.SQLException} faults.
*
* @return This environment's helper.
*/
public SqlExceptionHelper getSqlExceptionHelper();
SqlExceptionHelper getSqlExceptionHelper();
/**
* Retrieve the delegate for building {@link org.hibernate.engine.jdbc.LobCreator} instances.
*
* @return The LobCreator builder.
*/
public LobCreatorBuilder getLobCreatorBuilder();
LobCreatorBuilder getLobCreatorBuilder();
/**
* Find type information for the type identified by the given "JDBC type code".
@ -98,5 +89,5 @@ public interface JdbcEnvironment extends Service {
*
* @return The corresponding type info.
*/
public TypeInfo getTypeInfoForJdbcCode(int jdbcTypeCode);
TypeInfo getTypeInfoForJdbcCode(int jdbcTypeCode);
}

View File

@ -12,10 +12,38 @@ import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.dialect.Dialect;
/**
* Contract for rendering qualified object names for use in queries, etc.
*
* @author Steve Ebersole
*/
public interface QualifiedObjectNameFormatter {
public String format(QualifiedTableName qualifiedTableName, Dialect dialect);
public String format(QualifiedSequenceName qualifiedSequenceName, Dialect dialect);
public String format(QualifiedName qualifiedName, Dialect dialect);
/**
* Render a formatted a table name
*
* @param qualifiedTableName The table name
* @param dialect The dialect
*
* @return The formatted name,
*/
String format(QualifiedTableName qualifiedTableName, Dialect dialect);
/**
* Render a formatted sequence name
*
* @param qualifiedSequenceName The sequence name
* @param dialect The dialect
*
* @return The formatted name
*/
String format(QualifiedSequenceName qualifiedSequenceName, Dialect dialect);
/**
* Render a formatted non-table and non-sequence qualified name
*
* @param qualifiedName The name
* @param dialect The dialect
*
* @return The formatted name
*/
String format(QualifiedName qualifiedName, Dialect dialect);
}

View File

@ -13,8 +13,11 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
public final class StringHelper {
@ -796,4 +799,8 @@ public final class StringHelper {
public static String nullIfEmpty(String value) {
return isEmpty( value ) ? null : value;
}
public static List<String> parseCommaSeparatedString(String incomingString) {
return Arrays.asList( incomingString.split( "\\s*,\\s*" ) );
}
}

View File

@ -208,7 +208,7 @@ public final class ArrayHelper {
return true;
}
public static boolean isAllTrue(boolean[] array) {
public static boolean isAllTrue(boolean... array) {
for ( boolean anArray : array ) {
if ( !anArray ) {
return false;
@ -217,7 +217,7 @@ public final class ArrayHelper {
return true;
}
public static int countTrue(boolean[] array) {
public static int countTrue(boolean... array) {
int result = 0;
for ( boolean anArray : array ) {
if ( anArray ) {
@ -235,7 +235,7 @@ public final class ArrayHelper {
return result;
}*/
public static boolean isAllFalse(boolean[] array) {
public static boolean isAllFalse(boolean... array) {
for ( boolean anArray : array ) {
if ( anArray ) {
return false;

View File

@ -43,13 +43,9 @@ public class TestKeywordRecognition extends BaseUnitTestCase {
public void testAnsiSqlKeyword() {
// END is ANSI SQL keyword
// keywords are kept defined in upper case in here...
assertTrue( AnsiSqlKeywords.INSTANCE.sql2003().contains( "END" ) );
// But JdbcEnvironment uses a case-insensitive Set to store them...
JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class );
assertTrue( jdbcEnvironment.isReservedWord( "end" ) );
assertTrue( jdbcEnvironment.isReservedWord( "END" ) );
assertTrue( jdbcEnvironment.getIdentifierHelper().isReservedWord( "end" ) );
assertTrue( jdbcEnvironment.getIdentifierHelper().isReservedWord( "END" ) );
Identifier identifier = jdbcEnvironment.getIdentifierHelper().toIdentifier( "end" );
assertTrue( identifier.isQuoted() );