HHH-9951 - Formula annotation doesn't properly escape keywords/types

This commit is contained in:
Michael Hum 2015-07-18 21:18:22 -04:00 committed by Vlad Mihalcea
parent 6be82328b5
commit 5a47abbbfe
4 changed files with 239 additions and 7 deletions

View File

@ -646,6 +646,18 @@ public abstract class Dialect implements ConversionContext {
return result;
}
/**
* Whether or not the given type name has been registered for this dialect (including both hibernate type names and
* custom-registered type names).
*
* @param typeName the type name.
*
* @return true if the given string has been registered either as a hibernate type or as a custom-registered one
*/
public boolean isTypeNameRegistered(final String typeName) {
return this.typeNames.containsTypeName( typeName );
}
/**
* Get the name of the Hibernate {@link org.hibernate.type.Type} associated
* with the given {@link java.sql.Types} typecode with the given storage
@ -1746,7 +1758,9 @@ public abstract class Dialect implements ConversionContext {
// keyword support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
protected void registerKeyword(String word) {
sqlKeywords.add( word );
// When tokens are checked for keywords, they are always compared against the lower-case version of the token.
// For instance, Template#renderWhereStringTemplate transforms all tokens to lower-case too.
sqlKeywords.add(word.toLowerCase(Locale.ROOT));
}
/**

View File

@ -133,4 +133,15 @@ public class TypeNames {
public void put(int typeCode, String value) {
defaults.put( typeCode, value );
}
/**
* Check whether or not the provided typeName exists.
*
* @param typeName the type name.
*
* @return true if the given string has been registered as a type.
*/
public boolean containsTypeName(final String typeName) {
return this.defaults.containsValue( typeName );
}
}

View File

@ -718,12 +718,21 @@ public final class Template {
return token.startsWith( ":" );
}
private static boolean isFunctionOrKeyword(String lcToken, String nextToken, Dialect dialect, SQLFunctionRegistry functionRegistry) {
return "(".equals(nextToken) ||
KEYWORDS.contains(lcToken) ||
isFunction(lcToken, nextToken, functionRegistry ) ||
dialect.getKeywords().contains(lcToken) ||
FUNCTION_KEYWORDS.contains(lcToken);
private static boolean isFunctionOrKeyword(
String lcToken,
String nextToken,
Dialect dialect,
SQLFunctionRegistry functionRegistry) {
return "(".equals( nextToken ) ||
KEYWORDS.contains( lcToken ) ||
isType( lcToken, dialect ) ||
isFunction( lcToken, nextToken, functionRegistry ) ||
dialect.getKeywords().contains( lcToken ) ||
FUNCTION_KEYWORDS.contains( lcToken );
}
private static boolean isType(String lcToken, Dialect dialect) {
return dialect.isTypeNameRegistered( lcToken );
}
private static boolean isFunction(String lcToken, String nextToken, SQLFunctionRegistry functionRegistry) {

View File

@ -0,0 +1,198 @@
/*
* 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.annotations.formula;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.annotations.Formula;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.criterion.Order;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Test that the queries generated by {@link org.hibernate.annotations.Formula} properly ignore type names when escaping identifiers.
* <p/>
* Created by Michael Hum on 17/07/2015.
*/
@RequiresDialect(H2Dialect.class)
public class FormulaWithColumnTypesTest extends BaseCoreFunctionalTestCase {
@Override
protected void configure(Configuration configuration) {
configuration.setProperty(
Environment.DIALECT,
ExtendedDialect.class.getName()
);
}
@Test
@TestForIssue(jiraKey = "HHH-9951")
public void testFormulaAnnotationWithTypeNames() {
Session session = openSession();
Transaction transaction = session.beginTransaction();
DisplayItem displayItem20 = new DisplayItem();
displayItem20.setDisplayCode( "20" );
DisplayItem displayItem03 = new DisplayItem();
displayItem03.setDisplayCode( "03" );
DisplayItem displayItem100 = new DisplayItem();
displayItem100.setDisplayCode( "100" );
session.persist( displayItem20 );
session.persist( displayItem03 );
session.persist( displayItem100 );
transaction.commit();
session.close();
// 1. Default sorting by display code natural ordering (resulting in 3-100-20).
session = openSession();
transaction = session.beginTransaction();
List displayItems = session.createCriteria( DisplayItem.class )
.addOrder( Order.asc( "displayCode" ) )
.list();
assertNotNull( displayItems );
assertEquals( displayItems.size(), 3 );
assertEquals(
"03",
( (DisplayItem) displayItems.get( 0 ) ).getDisplayCode()
);
assertEquals(
"100",
( (DisplayItem) displayItems.get( 1 ) ).getDisplayCode()
);
assertEquals(
"20",
( (DisplayItem) displayItems.get( 2 ) ).getDisplayCode()
);
transaction.commit();
session.close();
// 2. Sorting by the casted type (resulting in 3-20-100).
session = openSession();
transaction = session.beginTransaction();
List displayItemsSortedByInteger = session.createCriteria( DisplayItem.class )
.addOrder( Order.asc( "displayCodeAsInteger" ) )
.list();
assertNotNull( displayItemsSortedByInteger );
assertEquals( displayItemsSortedByInteger.size(), 3 );
assertEquals(
"03",
( (DisplayItem) displayItemsSortedByInteger.get( 0 ) ).getDisplayCode()
);
assertEquals(
"20",
( (DisplayItem) displayItemsSortedByInteger.get( 1 ) ).getDisplayCode()
);
assertEquals(
"100",
( (DisplayItem) displayItemsSortedByInteger.get( 2 ) ).getDisplayCode()
);
transaction.commit();
session.close();
}
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {DisplayItem.class};
}
/**
* Test entity for formulas.
* <p>
* INTEGER is registered as a keyword for testing lower-case sensitivity.
* FLOAT is registered as a valid column type with oracle dialects.
* <p>
* Created by Michael Hum on 17/07/2015.
*/
@Entity(name = "DisplayItem")
public static class DisplayItem implements Serializable {
private int id;
private String displayCode;
private Integer displayCodeAsInteger;
private Integer displayCodeAsFloat;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "DISPLAY_CODE")
public String getDisplayCode() {
return this.displayCode;
}
public void setDisplayCode(final String displayCode) {
this.displayCode = displayCode;
}
@Formula("CAST(DISPLAY_CODE AS FLOAT)")
public Integer getDisplayCodeAsFloat() {
return displayCodeAsFloat;
}
public void setDisplayCodeAsFloat(final Integer displayCodeAsFloat) {
this.displayCodeAsFloat = displayCodeAsFloat;
}
@Formula("CAST(DISPLAY_CODE AS INTEGER)")
public Integer getDisplayCodeAsInteger() {
return displayCodeAsInteger;
}
public void setDisplayCodeAsInteger(final Integer displayCodeAsInteger) {
this.displayCodeAsInteger = displayCodeAsInteger;
}
}
/**
* Dialect for test case where we register a keyword and see if it gets escaped or not.
* <p>
* Created by Mike on 18/07/2015.
*/
public static class ExtendedDialect extends H2Dialect {
public ExtendedDialect() {
super();
registerKeyword( "INTEGER" );
}
}
}