Skip to content

Commit

Permalink
HHH-9951 - Formula annotation doesn't properly escape keywords/types
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Hum authored and vladmihalcea committed May 24, 2016
1 parent 6be8232 commit 5a47abb
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 7 deletions.
16 changes: 15 additions & 1 deletion hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,18 @@ public String getHibernateTypeName(int code) throws HibernateException {
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
Expand Down Expand Up @@ -1746,7 +1758,9 @@ public String toBooleanValueString(boolean bool) {
// 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));
}

/**
Expand Down
11 changes: 11 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,15 @@ public void put(int typeCode, long capacity, String value) {
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 );
}
}
21 changes: 15 additions & 6 deletions hibernate-core/src/main/java/org/hibernate/sql/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -718,12 +718,21 @@ private static boolean isNamedParameter(String token) {
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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" );
}

}
}

0 comments on commit 5a47abb

Please sign in to comment.