HHH-9768 - Maintain explicit list of ANSI SQL keywords

This commit is contained in:
Steve Ebersole 2015-05-01 14:08:08 -05:00
parent 490fb1cf7b
commit dd314f492d
13 changed files with 202 additions and 77 deletions

View File

@ -130,10 +130,20 @@ class HibernateBuildPlugin implements Plugin<Project> {
SourceSet mainSourceSet = project.getConvention().findPlugin( JavaPluginConvention.class ).sourceSets.findByName( "main" )
JavaCompile compileTask = project.tasks.findByName( mainSourceSet.compileJavaTaskName ) as JavaCompile
// NOTE : this aptDir stuff is needed until we can have IntelliJ run annotation processors for us
// which cannot happen until we can fold hibernate-testing back into hibernate-core/src/test
// which cannot happen until... ugh
File aptDir = project.file( "${project.buildDir}/generated-src/apt/main" )
mainSourceSet.allJava.srcDir( aptDir )
compileTask.options.compilerArgs += [
"-nowarn",
"-encoding", "UTF-8"
"-encoding", "UTF-8",
"-s", "${aptDir.absolutePath}"
]
compileTask.doFirst {
aptDir.mkdirs()
}
if ( javaTargetExtension.version.java8Compatible ) {
compileTask.options.compilerArgs += [
@ -161,6 +171,25 @@ class HibernateBuildPlugin implements Plugin<Project> {
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Apply to test compile task
SourceSet testSourceSet = project.getConvention().findPlugin( JavaPluginConvention.class ).sourceSets.findByName( "test" )
JavaCompile compileTestTask = project.tasks.findByName( testSourceSet.compileJavaTaskName ) as JavaCompile
// NOTE : see the note abovewrt aptDir
File testAptDir = project.file( "${project.buildDir}/generated-src/apt/test" )
testSourceSet.allJava.srcDir( testAptDir )
compileTestTask.options.compilerArgs += [
"-nowarn",
"-encoding", "UTF-8",
"-s", "${testAptDir.absolutePath}"
]
compileTestTask.doFirst {
testAptDir.mkdirs()
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Apply to test tasks

View File

@ -110,6 +110,12 @@ public abstract class ObjectNameNormalizer {
return logicalName;
}
public String applyGlobalQuoting(String text) {
return database().getJdbcEnvironment().getIdentifierHelper().applyGlobalQuoting( text )
.render( database().getDialect() );
}
/**
* Access the contextual information related to the current process of building metadata. Here,
* that typically might be needed for accessing:<ul>

View File

@ -555,7 +555,7 @@ public class Ejb3Column {
sqlType = null;
}
else {
sqlType = normalizer.toDatabaseIdentifierText( col.columnDefinition() );
sqlType = normalizer.applyGlobalQuoting( col.columnDefinition() );
}
final String tableName;

View File

@ -69,6 +69,7 @@ import org.hibernate.dialect.unique.DefaultUniqueDelegate;
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.NameQualifierSupport;
import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver;
import org.hibernate.engine.spi.SessionImplementor;
@ -713,7 +714,7 @@ public abstract class Dialect implements ConversionContext {
protected void registerFunction(String name, SQLFunction function) {
// HHH-7721: SQLFunctionRegistry expects all lowercase. Enforce,
// just in case a user's customer dialect uses mixed cases.
sqlFunctions.put( name.toLowerCase(Locale.ROOT), function );
sqlFunctions.put( name.toLowerCase( Locale.ROOT ), function );
}
/**
@ -727,17 +728,6 @@ public abstract class Dialect implements ConversionContext {
}
// keyword support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
protected void registerKeyword(String word) {
sqlKeywords.add( word );
}
public Set<String> getKeywords() {
return sqlKeywords;
}
// native identifier generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
@ -1863,6 +1853,43 @@ public abstract class Dialect implements ConversionContext {
}
// keyword support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
protected void registerKeyword(String word) {
sqlKeywords.add( word );
}
/**
* @deprecated See {@link #determineKeywordsForAutoQuoting} instead
*/
@Deprecated
public Set<String> getKeywords() {
return sqlKeywords;
}
/**
* 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>
* </ul>
* NOTE: The code that ultimately consumes these and uses them stores them in a case-insensitive
* Set, so case here does not matter.
*
* @see org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords#sql2003()
* @see java.sql.DatabaseMetaData#getSQLKeywords()
* @see #registerKeyword
*/
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;
}
// identifier quoting support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**

View File

@ -26,9 +26,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.Locale;
import java.util.Set;
import java.util.TreeSet;
@ -38,7 +38,6 @@ import org.hibernate.cfg.AvailableSettings;
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.AnsiSqlKeywords;
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
@ -89,8 +88,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
this.sqlExceptionHelper = buildSqlExceptionHelper( dialect );
this.extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl.Builder( this ).build();
reservedWords.addAll( AnsiSqlKeywords.INSTANCE.sql2003() );
reservedWords.addAll( dialect.getKeywords() );
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( Collections.<String>emptySet() ) );
final boolean globallyQuoteIdentifiers = serviceRegistry.getService( ConfigurationService.class )
.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false );
@ -142,9 +140,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
nameQualifierSupport = determineNameQualifierSupport( databaseMetaData );
}
reservedWords.addAll( AnsiSqlKeywords.INSTANCE.sql2003() );
reservedWords.addAll( dialect.getKeywords() );
reservedWords.addAll( extractedMetaDataSupport.getExtraKeywords() );
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( extractedMetaDataSupport.getExtraKeywords() ) );
final boolean globallyQuoteIdentifiers = false;
@ -217,9 +213,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
nameQualifierSupport = determineNameQualifierSupport( databaseMetaData );
}
reservedWords.addAll( AnsiSqlKeywords.INSTANCE.sql2003() );
reservedWords.addAll( dialect.getKeywords() );
reservedWords.addAll( extractedMetaDataSupport.getExtraKeywords() );
reservedWords.addAll( dialect.determineKeywordsForAutoQuoting( extractedMetaDataSupport.getExtraKeywords() ) );
final boolean globallyQuoteIdentifiers = serviceRegistry.getService( ConfigurationService.class )
.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false );

View File

@ -124,6 +124,11 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
return normalizeQuoting( Identifier.toIdentifier( text, quoted ) );
}
@Override
public Identifier applyGlobalQuoting(String text) {
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

View File

@ -69,6 +69,17 @@ public interface IdentifierHelper {
*/
public 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.
*
* @param text The text to be (possibly) quoted
*
* @return The identifier form
*/
public Identifier applyGlobalQuoting(String text);
/**
* Render the Identifier representation of a catalog name into the String form needed
* in {@link java.sql.DatabaseMetaData} calls.

View File

@ -1,45 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.resource.transaction.backend.jta.internal;
import org.jboss.logging.Logger;
import org.jboss.logging.annotations.MessageLogger;
import org.jboss.logging.annotations.ValidIdRange;
/**
* Acts as the {@link org.jboss.logging.annotations.MessageLogger} related to
* JTA transaction processing
*
* @author Steve Ebersole
*/
@MessageLogger( projectCode = "HHH" )
@ValidIdRange( min = 10001001, max = 10002000 )
public interface JtaMessageLogger {
public static final JtaMessageLogger INSTANCE = Logger.getMessageLogger(
JtaMessageLogger.class,
"org.hibernate.orm.txn.backend.jta"
);
}

View File

@ -0,0 +1,74 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.jdbc.env;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class TestKeywordRecognition extends BaseUnitTestCase {
private StandardServiceRegistry serviceRegistry;
@Before
public void prepareServiveRegistry() {
serviceRegistry = new StandardServiceRegistryBuilder().build();
}
@After
public void releaseServiveRegistry() {
if ( serviceRegistry != null ) {
StandardServiceRegistryBuilder.destroy( serviceRegistry );
}
}
@Test
@TestForIssue( jiraKey = "HHH_9768" )
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" ) );
Identifier identifier = jdbcEnvironment.getIdentifierHelper().toIdentifier( "end" );
assertTrue( identifier.isQuoted() );
}
}

View File

@ -17,7 +17,7 @@
<sql-query name="loadC">
<return alias="c" class="CompositeIdId"/>
select system as {c.system}, id as {c.id}, name as {c.name}, foo as {c.composite.foo}, bar as {c.composite.bar} from CompositeIdId where system=? and id=?
select "system" as {c.system}, id as {c.id}, name as {c.name}, foo as {c.composite.foo}, bar as {c.composite.bar} from CompositeIdId where "system"=? and id=?
</sql-query>
</hibernate-mapping>

View File

@ -6,21 +6,24 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Test;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.TimesTenDialect;
import org.hibernate.testing.DialectCheck;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -695,8 +698,17 @@ public class SQLLoaderTest extends LegacyTestCase {
session.close();
}
public static class DoubleQuoteDialect implements DialectCheck {
@Override
public boolean isMatch(Dialect dialect) {
return '"' == dialect.openQuote() && '"' == dialect.closeQuote();
}
}
@Test
@TestForIssue( jiraKey = "HHH-21" )
// because the XML mapping defines the loader for CompositeIdId using a column name that needs to be quoted
@RequiresDialectFeature( DoubleQuoteDialect.class )
public void testCompositeIdId() throws HibernateException, SQLException {
Session s = openSession();
s.beginTransaction();
@ -711,7 +723,17 @@ public class SQLLoaderTest extends LegacyTestCase {
s = openSession();
s.beginTransaction();
// having a composite id with one property named id works since the map used by sqlloader to map names to properties handles it.
String sql = "select system as {c.system}, id as {c.id}, name as {c.name}, foo as {c.composite.foo}, bar as {c.composite.bar} from CompositeIdId where system=? and id=?";
// NOTE : SYSTEM is an ANSI SQL defined keyword, so it gets quoted; so it needs to get quoted here too
String sql = String.format(
"select %1$s as {c.system}, " +
" id as {c.id}, name as {c.name}, " +
" foo as {c.composite.foo}, " +
" bar as {c.composite.bar} " +
"from CompositeIdId " +
"where %1$s=? and id=?",
getDialect().openQuote() + "system" + getDialect().closeQuote()
);
SQLQuery query = s.createSQLQuery( sql ).addEntity( "c", CompositeIdId.class );
query.setString(0, "c64");
query.setString(1, "games");

View File

@ -85,7 +85,7 @@ public class LegacyJpaNamingWithAnnotationBindingTests extends BaseAnnotationBin
@Override
protected void validateOrderPrimaryTableName(String name) {
assertEquals( "Order", name );
assertEquals( "`Order`", name );
}
@Override
@ -189,7 +189,7 @@ public class LegacyJpaNamingWithAnnotationBindingTests extends BaseAnnotationBin
@Override
protected void validateCustomerOrdersTableName(String name) {
assertEquals( "Order", name );
assertEquals( "`Order`", name );
}
@Override

View File

@ -91,7 +91,8 @@ public class LegacyJpaNamingWithHbmBindingTests extends BaseHbmBindingTests {
@Override
protected void validateOrderPrimaryTableName(String name) {
assertEquals( "Order", name );
// quoted because "order" is an ansi sql keyword
assertEquals( "`Order`", name );
}
@Override
@ -191,7 +192,8 @@ public class LegacyJpaNamingWithHbmBindingTests extends BaseHbmBindingTests {
@Override
protected void validateCustomerOrdersTableName(String name) {
assertEquals( "Order", name );
// quoted because "order" is an ansi sql keyword
assertEquals( "`Order`", name );
}
@Override