From 9b3c518d5e20c2f30b286d2caa93c6df3b59434a Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 11 Jan 2010 22:55:36 +0000 Subject: [PATCH] HHH-4780 - Allow BigDecimal and BigInteger to be specified as numeric literal types git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18508 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- core/src/main/antlr/hql-sql.g | 2 + core/src/main/antlr/hql.g | 25 ++-- core/src/main/antlr/sql-gen.g | 2 + .../org/hibernate/hql/ast/SqlASTFactory.java | 2 + .../hibernate/hql/ast/tree/LiteralNode.java | 5 +- .../hql/ast/util/LiteralProcessor.java | 33 ++++- .../ejb/criteria/ValueHandlerFactory.java | 10 ++ .../test/hql/ASTParserLoadingTest.java | 122 +++++++++++++++++- 8 files changed, 182 insertions(+), 19 deletions(-) diff --git a/core/src/main/antlr/hql-sql.g b/core/src/main/antlr/hql-sql.g index ac1dd2c33e..3e177b3540 100644 --- a/core/src/main/antlr/hql-sql.g +++ b/core/src/main/antlr/hql-sql.g @@ -598,6 +598,8 @@ literal | NUM_LONG { processNumericLiteral( #literal ); } | NUM_FLOAT { processNumericLiteral( #literal ); } | NUM_DOUBLE { processNumericLiteral( #literal ); } + | NUM_BIG_INTEGER { processNumericLiteral( #literal ); } + | NUM_BIG_DECIMAL { processNumericLiteral( #literal ); } | QUOTED_STRING ; diff --git a/core/src/main/antlr/hql.g b/core/src/main/antlr/hql.g index b7b220c136..4b2d4b5181 100644 --- a/core/src/main/antlr/hql.g +++ b/core/src/main/antlr/hql.g @@ -133,6 +133,8 @@ tokens NUM_DOUBLE; NUM_FLOAT; NUM_LONG; + NUM_BIG_INTEGER; + NUM_BIG_DECIMAL; JAVA_CONSTANT; } @@ -693,6 +695,8 @@ constant | NUM_FLOAT | NUM_LONG | NUM_DOUBLE + | NUM_BIG_INTEGER + | NUM_BIG_DECIMAL | QUOTED_STRING | NULL | TRUE @@ -821,12 +825,13 @@ NUM_INT : '.' {_ttype = DOT;} ( ('0'..'9')+ (EXPONENT)? (f1:FLOAT_SUFFIX {t=f1;})? { - if (t != null && t.getText().toUpperCase().indexOf('F')>=0) - { + if ( t != null && t.getText().toUpperCase().indexOf("BD")>=0) { + _ttype = NUM_BIG_DECIMAL; + } + else if (t != null && t.getText().toUpperCase().indexOf('F')>=0) { _ttype = NUM_FLOAT; } - else - { + else { _ttype = NUM_DOUBLE; // assume double } } @@ -847,6 +852,7 @@ NUM_INT | ('1'..'9') ('0'..'9')* {isDecimal=true;} // non-zero decimal ) ( ('l') { _ttype = NUM_LONG; } + | ('b''i') { _ttype = NUM_BIG_INTEGER; } // only check to see if it's a float if looks like decimal so far | {isDecimal}? @@ -855,12 +861,13 @@ NUM_INT | f4:FLOAT_SUFFIX {t=f4;} ) { - if (t != null && t.getText().toUpperCase() .indexOf('F') >= 0) - { + if ( t != null && t.getText().toUpperCase().indexOf("BD")>=0) { + _ttype = NUM_BIG_DECIMAL; + } + else if (t != null && t.getText().toUpperCase() .indexOf('F') >= 0) { _ttype = NUM_FLOAT; } - else - { + else { _ttype = NUM_DOUBLE; // assume double } } @@ -881,6 +888,6 @@ EXPONENT protected FLOAT_SUFFIX - : 'f'|'d' + : 'f'|'d'|'b''d' ; diff --git a/core/src/main/antlr/sql-gen.g b/core/src/main/antlr/sql-gen.g index 552a483038..064810ce3b 100644 --- a/core/src/main/antlr/sql-gen.g +++ b/core/src/main/antlr/sql-gen.g @@ -353,6 +353,8 @@ constant | NUM_FLOAT | NUM_INT | NUM_LONG + | NUM_BIG_INTEGER + | NUM_BIG_DECIMAL | QUOTED_STRING | CONSTANT | JAVA_CONSTANT diff --git a/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java b/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java index e6a8a0425b..3a057b6945 100644 --- a/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java +++ b/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java @@ -145,6 +145,8 @@ public class SqlASTFactory extends ASTFactory implements HqlSqlTokenTypes { case NUM_FLOAT: case NUM_LONG: case NUM_DOUBLE: + case NUM_BIG_INTEGER: + case NUM_BIG_DECIMAL: case QUOTED_STRING: return LiteralNode.class; case TRUE: diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/LiteralNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/LiteralNode.java index 8efe98ce24..e300821b2f 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/LiteralNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/LiteralNode.java @@ -22,7 +22,6 @@ * Boston, MA 02110-1301 USA * */ - package org.hibernate.hql.ast.tree; import org.hibernate.Hibernate; @@ -53,6 +52,10 @@ public class LiteralNode extends AbstractSelectExpression implements HqlSqlToken return Hibernate.LONG; case NUM_DOUBLE: return Hibernate.DOUBLE; + case NUM_BIG_INTEGER: + return Hibernate.BIG_INTEGER; + case NUM_BIG_DECIMAL: + return Hibernate.BIG_DECIMAL; case QUOTED_STRING: return Hibernate.STRING; case TRUE: diff --git a/core/src/main/java/org/hibernate/hql/ast/util/LiteralProcessor.java b/core/src/main/java/org/hibernate/hql/ast/util/LiteralProcessor.java index 91e16d82c4..186279002c 100644 --- a/core/src/main/java/org/hibernate/hql/ast/util/LiteralProcessor.java +++ b/core/src/main/java/org/hibernate/hql/ast/util/LiteralProcessor.java @@ -50,6 +50,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; +import java.math.BigInteger; import java.text.DecimalFormat; /** @@ -229,10 +230,14 @@ public class LiteralProcessor implements HqlSqlTokenTypes { } public void processNumeric(AST literal) { - if ( literal.getType() == NUM_INT || literal.getType() == NUM_LONG ) { + if ( literal.getType() == NUM_INT + || literal.getType() == NUM_LONG + || literal.getType() == NUM_BIG_INTEGER ) { literal.setText( determineIntegerRepresentation( literal.getText(), literal.getType() ) ); } - else if ( literal.getType() == NUM_FLOAT || literal.getType() == NUM_DOUBLE ) { + else if ( literal.getType() == NUM_FLOAT + || literal.getType() == NUM_DOUBLE + || literal.getType() == NUM_BIG_DECIMAL ) { literal.setText( determineDecimalRepresentation( literal.getText(), literal.getType() ) ); } else { @@ -250,11 +255,21 @@ public class LiteralProcessor implements HqlSqlTokenTypes { log.trace( "could not format incoming text [" + text + "] as a NUM_INT; assuming numeric overflow and attempting as NUM_LONG" ); } } - String literalValue = text; - if ( literalValue.endsWith( "l" ) || literalValue.endsWith( "L" ) ) { - literalValue = literalValue.substring( 0, literalValue.length() - 1 ); + else if ( type == NUM_LONG ) { + String literalValue = text; + if ( literalValue.endsWith( "l" ) || literalValue.endsWith( "L" ) ) { + literalValue = literalValue.substring( 0, literalValue.length() - 1 ); + } + return Long.valueOf( literalValue ).toString(); } - return Long.valueOf( literalValue ).toString(); + else if ( type == NUM_BIG_INTEGER ) { + String literalValue = text; + if ( literalValue.endsWith( "bi" ) || literalValue.endsWith( "BI" ) ) { + literalValue = literalValue.substring( 0, literalValue.length() - 2 ); + } + return new BigInteger( literalValue ).toString(); + } + throw new HibernateException( "Unknown literal type to parse as integer" ); } catch( Throwable t ) { throw new HibernateException( "Could not parse literal [" + text + "] as integer", t ); @@ -273,6 +288,11 @@ public class LiteralProcessor implements HqlSqlTokenTypes { literalValue = literalValue.substring( 0, literalValue.length() - 1 ); } } + else if ( type == NUM_BIG_DECIMAL ) { + if ( literalValue.endsWith( "bd" ) || literalValue.endsWith( "BD" ) ) { + literalValue = literalValue.substring( 0, literalValue.length() - 2 ); + } + } BigDecimal number = null; try { @@ -285,6 +305,7 @@ public class LiteralProcessor implements HqlSqlTokenTypes { return formatters[ DECIMAL_LITERAL_FORMAT ].format( number ); } + private static interface DecimalFormatter { String format(BigDecimal number); } diff --git a/entitymanager/src/main/java/org/hibernate/ejb/criteria/ValueHandlerFactory.java b/entitymanager/src/main/java/org/hibernate/ejb/criteria/ValueHandlerFactory.java index f3b8c69687..2a5520bf98 100644 --- a/entitymanager/src/main/java/org/hibernate/ejb/criteria/ValueHandlerFactory.java +++ b/entitymanager/src/main/java/org/hibernate/ejb/criteria/ValueHandlerFactory.java @@ -217,6 +217,11 @@ public class ValueHandlerFactory { } throw unknownConversion( value, BigInteger.class ); } + + @Override + public String render(BigInteger value) { + return "cast( " + value.toString() + " as BigInteger )"; + } } public static class BigDecimalValueHandler extends BaseValueHandler implements Serializable { @@ -236,6 +241,11 @@ public class ValueHandlerFactory { } throw unknownConversion( value, BigDecimal.class ); } + + @Override + public String render(BigDecimal value) { + return "cast( " + value.toString() + " as BigDecimal )"; + } } public static class StringValueHandler extends BaseValueHandler implements Serializable { diff --git a/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index d21e17d507..39792976e5 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -2,6 +2,7 @@ package org.hibernate.test.hql; import java.math.BigDecimal; +import java.math.BigInteger; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -1195,9 +1196,124 @@ public class ASTParserLoadingTest extends FunctionalTestCase { a.setBodyWeight(12.4f); a.setDescription("an animal"); s.persist(a); - Integer bw = (Integer) s.createQuery("select cast(bodyWeight as integer) from Animal").uniqueResult(); - bw = (Integer) s.createQuery("select cast(a.bodyWeight as integer) from Animal a").uniqueResult(); - bw.toString(); + Object bodyWeight = s.createQuery("select cast(bodyWeight as integer) from Animal").uniqueResult(); + assertTrue( Integer.class.isInstance( bodyWeight ) ); + assertEquals( 12, bodyWeight ); + bodyWeight = s.createQuery("select cast(bodyWeight as big_decimal) from Animal").uniqueResult(); + assertTrue( BigDecimal.class.isInstance( bodyWeight ) ); + assertEquals( BigDecimal.valueOf( a.getBodyWeight() ), bodyWeight ); + Object literal = s.createQuery("select cast(10000000 as big_integer) from Animal").uniqueResult(); + assertTrue( BigInteger.class.isInstance( literal ) ); + assertEquals( BigInteger.valueOf( 10000000 ), literal ); + s.delete(a); + t.commit(); + s.close(); + } + + /** + * Test the numeric expression rules specified in section 4.8.6 of the JPA 2 specification + */ + public void testNumericExpressionReturnTypes() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + Animal a = new Animal(); + a.setBodyWeight(12.4f); + a.setDescription("an animal"); + s.persist(a); + + Object result; + + // addition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + result = s.createQuery( "select 1 + 1 from Animal as a" ).uniqueResult(); + assertTrue( "int + int", Integer.class.isInstance( result ) ); + assertEquals( 2, result ); + + result = s.createQuery( "select 1 + 1L from Animal a" ).uniqueResult(); + assertTrue( "int + long", Long.class.isInstance( result ) ); + assertEquals( Long.valueOf( 2 ), result ); + + result = s.createQuery( "select 1 + 1BI from Animal a" ).uniqueResult(); + assertTrue( "int + BigInteger", BigInteger.class.isInstance( result ) ); + assertEquals( BigInteger.valueOf( 2 ), result ); + + result = s.createQuery( "select 1 + 1F from Animal a" ).uniqueResult(); + assertTrue( "int + float", Float.class.isInstance( result ) ); + assertEquals( Float.valueOf( 2 ), result ); + + result = s.createQuery( "select 1 + 1D from Animal a" ).uniqueResult(); + assertTrue( "int + double", Double.class.isInstance( result ) ); + assertEquals( Double.valueOf( 2 ), result ); + + result = s.createQuery( "select 1 + 1BD from Animal a" ).uniqueResult(); + assertTrue( "int + BigDecimal", BigDecimal.class.isInstance( result ) ); + assertEquals( BigDecimal.valueOf( 2 ), result ); + + result = s.createQuery( "select 1F + 1D from Animal a" ).uniqueResult(); + assertTrue( "float + double", Double.class.isInstance( result ) ); + assertEquals( Double.valueOf( 2 ), result ); + + result = s.createQuery( "select 1F + 1BD from Animal a" ).uniqueResult(); + assertTrue( "float + BigDecimal", Float.class.isInstance( result ) ); + assertEquals( Float.valueOf( 2 ), result ); + + // subtraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + result = s.createQuery( "select 1 - 1 from Animal as a" ).uniqueResult(); + assertTrue( "int - int", Integer.class.isInstance( result ) ); + assertEquals( 0, result ); + + result = s.createQuery( "select 1 - 1L from Animal a" ).uniqueResult(); + assertTrue( "int - long", Long.class.isInstance( result ) ); + assertEquals( Long.valueOf( 0 ), result ); + + result = s.createQuery( "select 1 - 1BI from Animal a" ).uniqueResult(); + assertTrue( "int - BigInteger", BigInteger.class.isInstance( result ) ); + assertEquals( BigInteger.valueOf( 0 ), result ); + + result = s.createQuery( "select 1 - 1F from Animal a" ).uniqueResult(); + assertTrue( "int - float", Float.class.isInstance( result ) ); + assertEquals( Float.valueOf( 0 ), result ); + + result = s.createQuery( "select 1 - 1D from Animal a" ).uniqueResult(); + assertTrue( "int - double", Double.class.isInstance( result ) ); + assertEquals( Double.valueOf( 0 ), result ); + + result = s.createQuery( "select 1 - 1BD from Animal a" ).uniqueResult(); + assertTrue( "int - BigDecimal", BigDecimal.class.isInstance( result ) ); + assertEquals( BigDecimal.valueOf( 0 ), result ); + + result = s.createQuery( "select 1F - 1D from Animal a" ).uniqueResult(); + assertTrue( "float - double", Double.class.isInstance( result ) ); + assertEquals( Double.valueOf( 0 ), result ); + + result = s.createQuery( "select 1F - 1BD from Animal a" ).uniqueResult(); + assertTrue( "float - BigDecimal", Float.class.isInstance( result ) ); + assertEquals( Float.valueOf( 0 ), result ); + + // multiplication ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + result = s.createQuery( "select 1 * 1 from Animal as a" ).uniqueResult(); + assertTrue( "int * int", Integer.class.isInstance( result ) ); + assertEquals( 1, result ); + + result = s.createQuery( "select 1 * 1L from Animal a" ).uniqueResult(); + assertTrue( "int * long", Long.class.isInstance( result ) ); + assertEquals( Long.valueOf( 1 ), result ); + + result = s.createQuery( "select 1 * 1BI from Animal a" ).uniqueResult(); + assertTrue( "int * BigInteger", BigInteger.class.isInstance( result ) ); + assertEquals( BigInteger.valueOf( 1 ), result ); + + result = s.createQuery( "select 1 * 1F from Animal a" ).uniqueResult(); + assertTrue( "int * float", Float.class.isInstance( result ) ); + assertEquals( Float.valueOf( 1 ), result ); + + result = s.createQuery( "select 1 * 1D from Animal a" ).uniqueResult(); + assertTrue( "int * double", Double.class.isInstance( result ) ); + assertEquals( Double.valueOf( 1 ), result ); + + result = s.createQuery( "select 1 * 1BD from Animal a" ).uniqueResult(); + assertTrue( "int * BigDecimal", BigDecimal.class.isInstance( result ) ); + assertEquals( BigDecimal.valueOf( 1 ), result ); + s.delete(a); t.commit(); s.close();