Skip to content

Commit a51f300

Browse files
committed
HHH-9820 - Handle JDBC drivers that do not properly report metadata regarding case of identifiers
1 parent d58ef69 commit a51f300

File tree

12 files changed

+500
-217
lines changed

12 files changed

+500
-217
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.sql.Blob;
1212
import java.sql.CallableStatement;
1313
import java.sql.Clob;
14+
import java.sql.DatabaseMetaData;
1415
import java.sql.NClob;
1516
import java.sql.ResultSet;
1617
import java.sql.SQLException;
@@ -53,6 +54,8 @@
5354
import org.hibernate.engine.jdbc.LobCreator;
5455
import org.hibernate.engine.jdbc.env.internal.DefaultSchemaNameResolver;
5556
import org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords;
57+
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
58+
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
5659
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
5760
import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver;
5861
import org.hibernate.engine.jdbc.spi.JdbcServices;
@@ -1846,37 +1849,55 @@ protected void registerKeyword(String word) {
18461849
}
18471850

18481851
/**
1849-
* @deprecated See {@link #determineKeywordsForAutoQuoting} instead
1852+
* @deprecated These are only ever used (if at all) from the code that handles identifier quoting. So
1853+
* see {@link #buildIdentifierHelper} instead
18501854
*/
18511855
@Deprecated
18521856
public Set<String> getKeywords() {
18531857
return sqlKeywords;
18541858
}
18551859

18561860
/**
1857-
* Hook into auto-quoting of identifiers that are deemed to be keywords. By default we
1858-
* return all of the following here:<ul>
1859-
* <li>all keywords as defined by ANSI SQL:2003 specification</li>
1860-
* <li>all "extra" keywords reported by the JDBC driver </li>
1861-
* <li>all Dialect-registered "extra" keywords</li>
1862-
* </ul>
1861+
* Build the IdentifierHelper indicated by this Dialect for handling identifier conversions.
1862+
* Returning {@code null} is allowed and indicates that Hibernate should fallback to building a
1863+
* "standard" helper. In the fallback path, any changes made to the IdentifierHelperBuilder
1864+
* during this call will still be incorporated into the built IdentifierHelper.
18631865
* <p/>
1864-
* Subclasses are free to override this as they see fit. Just be aware that overriding this
1865-
* does affect what identifiers are auto-quoted based on being seen as a keyword.
1866+
* The incoming builder will have the following set:<ul>
1867+
* <li>{@link IdentifierHelperBuilder#isGloballyQuoteIdentifiers()}</li>
1868+
* <li>{@link IdentifierHelperBuilder#getUnquotedCaseStrategy()} - initialized to UPPER</li>
1869+
* <li>{@link IdentifierHelperBuilder#getQuotedCaseStrategy()} - initialized to MIXED</li>
1870+
* </ul>
18661871
* <p/>
1867-
* NOTE: The code that ultimately consumes these and uses them stores them in a case-insensitive
1868-
* Set, so case here does not matter.
1869-
*
1870-
* @see org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords#sql2003()
1871-
* @see java.sql.DatabaseMetaData#getSQLKeywords()
1872-
* @see #registerKeyword
1873-
*/
1874-
public Set<String> determineKeywordsForAutoQuoting(Set<String> databaseMetadataReportedKeywords) {
1875-
final Set<String> keywords = new HashSet<String>();
1876-
keywords.addAll( AnsiSqlKeywords.INSTANCE.sql2003() );
1877-
keywords.addAll( databaseMetadataReportedKeywords );
1878-
keywords.addAll( sqlKeywords );
1879-
return keywords;
1872+
* By default Hibernate will do the following:<ul>
1873+
* <li>Call {@link IdentifierHelperBuilder#applyIdentifierCasing(DatabaseMetaData)}
1874+
* <li>Call {@link IdentifierHelperBuilder#applyReservedWords(DatabaseMetaData)}
1875+
* <li>Applies {@link AnsiSqlKeywords#sql2003()} as reserved words</li>
1876+
* <li>Applies the {#link #sqlKeywords} collected here as reserved words</li>
1877+
* <li>Applies the Dialect's NameQualifierSupport, if it defines one</li>
1878+
* </ul>
1879+
*
1880+
* @param builder A semi-configured IdentifierHelper builder.
1881+
* @param dbMetaData Access to the metadata returned from the driver if needed and if available. WARNING: may be {@code null}
1882+
*
1883+
* @return The IdentifierHelper instance to use, or {@code null} to indicate Hibernate should use its fallback path
1884+
*
1885+
* @throws SQLException Accessing the DatabaseMetaData can throw it. Just re-throw and Hibernate will handle.
1886+
*
1887+
* @see #getNameQualifierSupport()
1888+
*/
1889+
public IdentifierHelper buildIdentifierHelper(
1890+
IdentifierHelperBuilder builder,
1891+
DatabaseMetaData dbMetaData) throws SQLException {
1892+
builder.applyIdentifierCasing( dbMetaData );
1893+
1894+
builder.applyReservedWords( dbMetaData );
1895+
builder.applyReservedWords( AnsiSqlKeywords.INSTANCE.sql2003() );
1896+
builder.applyReservedWords( sqlKeywords );
1897+
1898+
builder.setNameQualifierSupport( getNameQualifierSupport() );
1899+
1900+
return builder.build();
18801901
}
18811902

18821903

hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
import java.sql.DatabaseMetaData;
1010
import java.sql.SQLException;
1111
import java.util.Arrays;
12-
import java.util.Collections;
1312
import java.util.HashSet;
1413
import java.util.LinkedHashSet;
1514
import java.util.Set;
16-
import java.util.TreeSet;
1715

1816
import org.hibernate.boot.model.naming.Identifier;
1917
import org.hibernate.boot.registry.selector.spi.StrategySelector;
@@ -22,7 +20,9 @@
2220
import org.hibernate.engine.config.spi.ConfigurationService;
2321
import org.hibernate.engine.config.spi.StandardConverters;
2422
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
23+
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
2524
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
25+
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
2626
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
2727
import org.hibernate.engine.jdbc.env.spi.LobCreatorBuilder;
2828
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
@@ -36,10 +36,14 @@
3636
import org.hibernate.service.ServiceRegistry;
3737
import org.hibernate.service.spi.ServiceRegistryImplementor;
3838

39+
import org.jboss.logging.Logger;
40+
3941
/**
4042
* @author Steve Ebersole
4143
*/
4244
public class JdbcEnvironmentImpl implements JdbcEnvironment {
45+
private static final Logger log = Logger.getLogger( JdbcEnvironmentImpl.class );
46+
4347
private final Dialect dialect;
4448

4549
private final SqlExceptionHelper sqlExceptionHelper;
@@ -51,7 +55,6 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
5155
private final LobCreatorBuilderImpl lobCreatorBuilder;
5256

5357
private final LinkedHashSet<TypeInfo> typeInfoSet = new LinkedHashSet<TypeInfo>();
54-
private final Set<String> reservedWords = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER );
5558

5659
/**
5760
* Constructor form used when the JDBC {@link java.sql.DatabaseMetaData} is not available.
@@ -62,6 +65,8 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
6265
public JdbcEnvironmentImpl(ServiceRegistryImplementor serviceRegistry, Dialect dialect) {
6366
this.dialect = dialect;
6467

68+
final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class );
69+
6570
NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport();
6671
if ( nameQualifierSupport == null ) {
6772
// assume both catalogs and schemas are supported
@@ -71,39 +76,43 @@ public JdbcEnvironmentImpl(ServiceRegistryImplementor serviceRegistry, Dialect d
7176
this.sqlExceptionHelper = buildSqlExceptionHelper( dialect );
7277
this.extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl.Builder( this ).build();
7378

74-
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( Collections.<String>emptySet() ) );
79+
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
80+
identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) );
81+
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
7582

76-
final boolean globallyQuoteIdentifiers = serviceRegistry.getService( ConfigurationService.class )
77-
.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false );
78-
79-
// a simple impl that works on H2
80-
this.identifierHelper = new NormalizingIdentifierHelperImpl(
81-
this,
82-
nameQualifierSupport,
83-
globallyQuoteIdentifiers,
84-
true, // storesMixedCaseQuotedIdentifiers
85-
false, // storesLowerCaseQuotedIdentifiers
86-
false, // storesUpperCaseQuotedIdentifiers
87-
false, // storesMixedCaseIdentifiers
88-
true, // storesUpperCaseIdentifiers
89-
false // storesLowerCaseIdentifiers
90-
);
83+
IdentifierHelper identifierHelper = null;
84+
try {
85+
identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, null );
86+
}
87+
catch (SQLException sqle) {
88+
// should never ever happen
89+
log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle );
90+
}
91+
if ( identifierHelper == null ) {
92+
identifierHelper = identifierHelperBuilder.build();
93+
}
94+
this.identifierHelper = identifierHelper;
9195

9296
this.currentCatalog = identifierHelper.toIdentifier(
93-
serviceRegistry.getService( ConfigurationService.class )
94-
.getSetting( AvailableSettings.DEFAULT_CATALOG, StandardConverters.STRING )
97+
cfgService.getSetting( AvailableSettings.DEFAULT_CATALOG, StandardConverters.STRING )
9598
);
9699
this.currentSchema = Identifier.toIdentifier(
97-
serviceRegistry.getService( ConfigurationService.class )
98-
.getSetting( AvailableSettings.DEFAULT_SCHEMA, StandardConverters.STRING )
100+
cfgService.getSetting( AvailableSettings.DEFAULT_SCHEMA, StandardConverters.STRING )
99101
);
100102

101-
// again, a simple impl that works on H2
102103
this.qualifiedObjectNameFormatter = new QualifiedObjectNameFormatterStandardImpl( nameQualifierSupport );
103104

104105
this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder();
105106
}
106107

108+
private static boolean globalQuoting(ConfigurationService cfgService) {
109+
return cfgService.getSetting(
110+
AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS,
111+
StandardConverters.BOOLEAN,
112+
false
113+
);
114+
}
115+
107116
/**
108117
* Constructor form used from testing
109118
*
@@ -123,22 +132,20 @@ public JdbcEnvironmentImpl(DatabaseMetaData databaseMetaData, Dialect dialect) t
123132
nameQualifierSupport = determineNameQualifierSupport( databaseMetaData );
124133
}
125134

126-
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( extractedMetaDataSupport.getExtraKeywords() ) );
127-
128-
final boolean globallyQuoteIdentifiers = false;
129-
130-
// a simple impl that works on H2
131-
this.identifierHelper = new NormalizingIdentifierHelperImpl(
132-
this,
133-
nameQualifierSupport,
134-
globallyQuoteIdentifiers,
135-
true, // storesMixedCaseQuotedIdentifiers
136-
false, // storesLowerCaseQuotedIdentifiers
137-
false, // storesUpperCaseQuotedIdentifiers
138-
false, // storesMixedCaseIdentifiers
139-
true, // storesUpperCaseIdentifiers
140-
false // storesLowerCaseIdentifiers
141-
);
135+
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
136+
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
137+
IdentifierHelper identifierHelper = null;
138+
try {
139+
identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData );
140+
}
141+
catch (SQLException sqle) {
142+
// should never ever happen
143+
log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle );
144+
}
145+
if ( identifierHelper == null ) {
146+
identifierHelper = identifierHelperBuilder.build();
147+
}
148+
this.identifierHelper = identifierHelper;
142149

143150
this.currentCatalog = null;
144151
this.currentSchema = null;
@@ -184,6 +191,8 @@ public JdbcEnvironmentImpl(
184191
DatabaseMetaData databaseMetaData) throws SQLException {
185192
this.dialect = dialect;
186193

194+
final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class );
195+
187196
this.sqlExceptionHelper = buildSqlExceptionHelper( dialect );
188197

189198
this.extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl.Builder( this )
@@ -196,22 +205,21 @@ public JdbcEnvironmentImpl(
196205
nameQualifierSupport = determineNameQualifierSupport( databaseMetaData );
197206
}
198207

199-
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( extractedMetaDataSupport.getExtraKeywords() ) );
200-
201-
final boolean globallyQuoteIdentifiers = serviceRegistry.getService( ConfigurationService.class )
202-
.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false );
203-
204-
this.identifierHelper = new NormalizingIdentifierHelperImpl(
205-
this,
206-
nameQualifierSupport,
207-
globallyQuoteIdentifiers,
208-
databaseMetaData.storesMixedCaseQuotedIdentifiers(),
209-
databaseMetaData.storesLowerCaseQuotedIdentifiers(),
210-
databaseMetaData.storesUpperCaseQuotedIdentifiers(),
211-
databaseMetaData.storesMixedCaseIdentifiers(),
212-
databaseMetaData.storesUpperCaseIdentifiers(),
213-
databaseMetaData.storesLowerCaseIdentifiers()
214-
);
208+
final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this );
209+
identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) );
210+
identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport );
211+
IdentifierHelper identifierHelper = null;
212+
try {
213+
identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData );
214+
}
215+
catch (SQLException sqle) {
216+
// should never ever happen
217+
log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle );
218+
}
219+
if ( identifierHelper == null ) {
220+
identifierHelper = identifierHelperBuilder.build();
221+
}
222+
this.identifierHelper = identifierHelper;
215223

216224
// and that current-catalog and current-schema happen after it
217225
this.currentCatalog = identifierHelper.toIdentifier( extractedMetaDataSupport.getConnectionCatalogName() );
@@ -225,7 +233,7 @@ public JdbcEnvironmentImpl(
225233
this.typeInfoSet.addAll( TypeInfo.extractTypeInfo( databaseMetaData ) );
226234

227235
this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder(
228-
serviceRegistry.getService( ConfigurationService.class ).getSettings(),
236+
cfgService.getSettings(),
229237
databaseMetaData.getConnection()
230238
);
231239
}
@@ -307,11 +315,6 @@ public IdentifierHelper getIdentifierHelper() {
307315
return identifierHelper;
308316
}
309317

310-
@Override
311-
public boolean isReservedWord(String word) {
312-
return reservedWords.contains( word );
313-
}
314-
315318
@Override
316319
public SqlExceptionHelper getSqlExceptionHelper() {
317320
return sqlExceptionHelper;

0 commit comments

Comments
 (0)