HHH-9951 - Formula annotation doesn't properly escape keywords/types
This commit is contained in:
parent
6be82328b5
commit
5a47abbbfe
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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" );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue