HHH-17076 - Numeric literal typing

https://hibernate.atlassian.net/browse/HHH-17076
This commit is contained in:
Steve Ebersole 2023-08-14 11:52:42 -05:00
parent c7ed34d159
commit ab4ac5a64e
20 changed files with 507 additions and 293 deletions

View File

@ -62,9 +62,9 @@ INTEGER_LITERAL : INTEGER_NUMBER ('_' INTEGER_NUMBER)*;
LONG_LITERAL : INTEGER_NUMBER ('_' INTEGER_NUMBER)* LONG_SUFFIX;
FLOAT_LITERAL : FLOATING_POINT_NUMBER FLOAT_SUFFIX?;
FLOAT_LITERAL : FLOATING_POINT_NUMBER FLOAT_SUFFIX;
DOUBLE_LITERAL : FLOATING_POINT_NUMBER DOUBLE_SUFFIX;
DOUBLE_LITERAL : FLOATING_POINT_NUMBER DOUBLE_SUFFIX?;
BIG_INTEGER_LITERAL : INTEGER_NUMBER BIG_INTEGER_SUFFIX;

View File

@ -611,6 +611,7 @@ parameterOrIntegerLiteral
parameterOrNumberLiteral
: parameter
| INTEGER_LITERAL
| LONG_LITERAL
| FLOAT_LITERAL
| DOUBLE_LITERAL
;
@ -1006,7 +1007,7 @@ month: INTEGER_LITERAL;
day: INTEGER_LITERAL;
hour: INTEGER_LITERAL;
minute: INTEGER_LITERAL;
second: INTEGER_LITERAL | FLOAT_LITERAL;
second: INTEGER_LITERAL | DOUBLE_LITERAL;
zoneId
: IDENTIFIER (SLASH IDENTIFIER)?
| STRING_LITERAL;

View File

@ -145,6 +145,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
@ -1791,7 +1792,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitParameterOrIntegerLiteral(HqlParser.ParameterOrIntegerLiteralContext ctx) {
if ( ctx.INTEGER_LITERAL() != null ) {
return integralLiteral( ctx.INTEGER_LITERAL().getText() );
return integerLiteral( ctx.INTEGER_LITERAL().getText() );
}
else {
return (SqmExpression<?>) ctx.parameter().accept( this );
@ -1801,10 +1802,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitParameterOrNumberLiteral(HqlParser.ParameterOrNumberLiteralContext ctx) {
if ( ctx.INTEGER_LITERAL() != null ) {
return integralLiteral( ctx.INTEGER_LITERAL().getText() );
return integerLiteral( ctx.INTEGER_LITERAL().getText() );
}
if ( ctx.FLOAT_LITERAL() != null ) {
return numericLiteral( ctx.FLOAT_LITERAL().getText() );
return floatLiteral( ctx.FLOAT_LITERAL().getText() );
}
if ( ctx.DOUBLE_LITERAL() != null ) {
return doubleLiteral( ctx.DOUBLE_LITERAL().getText() );
@ -1816,9 +1817,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
if ( firstChild instanceof TerminalNode ) {
switch ( ( (TerminalNode) firstChild ).getSymbol().getType() ) {
case HqlParser.INTEGER_LITERAL:
return integralLiteral( firstChild.getText() );
return integerLiteral( firstChild.getText() );
case HqlParser.FLOAT_LITERAL:
return numericLiteral( firstChild.getText() );
return floatLiteral( firstChild.getText() );
case HqlParser.DOUBLE_LITERAL:
return doubleLiteral( firstChild.getText() );
default:
@ -3224,7 +3225,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
switch ( node.getSymbol().getType() ) {
case HqlParser.INTEGER_LITERAL:
return integralLiteral( text );
return integerLiteral( text );
case HqlParser.LONG_LITERAL:
return longLiteral( text );
case HqlParser.BIG_INTEGER_LITERAL:
@ -3232,7 +3233,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
case HqlParser.HEX_LITERAL:
return hexLiteral( text );
case HqlParser.FLOAT_LITERAL:
return numericLiteral( text );
return floatLiteral( text );
case HqlParser.DOUBLE_LITERAL:
return doubleLiteral( text );
case HqlParser.BIG_DECIMAL_LITERAL:
@ -3281,7 +3282,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
case HqlParser.JAVA_STRING_LITERAL:
return javaStringLiteral( node.getText() );
case HqlParser.INTEGER_LITERAL:
return integralLiteral( node.getText() );
return integerLiteral( node.getText() );
case HqlParser.LONG_LITERAL:
return longLiteral( node.getText() );
case HqlParser.BIG_INTEGER_LITERAL:
@ -3289,7 +3290,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
case HqlParser.HEX_LITERAL:
return hexLiteral( node.getText() );
case HqlParser.FLOAT_LITERAL:
return numericLiteral( node.getText() );
return floatLiteral( node.getText() );
case HqlParser.DOUBLE_LITERAL:
return doubleLiteral( node.getText() );
case HqlParser.BIG_DECIMAL_LITERAL:
@ -3621,235 +3622,67 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
);
}
private SqmLiteral<? extends Number> integralLiteral(String text) {
final String integralText = text.replace( "_", "" );
final int decimalNumbers = integralText.length() - ( integralText.charAt( 0 ) == '-' ? 1 : 0 );
if ( decimalNumbers > 19 ) {
try {
final BigInteger value = new BigInteger( integralText );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( BigInteger.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to BigInteger",
e
);
}
}
else if ( decimalNumbers > 10 ) {
try {
final Long value = Long.valueOf( integralText );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( Long.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
try {
final BigInteger value = new BigInteger( integralText );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( BigInteger.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e2) {
final LiteralNumberFormatException exception = new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Long or BigInteger",
e
);
exception.addSuppressed( e );
exception.addSuppressed( e2 );
throw exception;
}
}
}
try {
final Integer value = Integer.valueOf( integralText );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
try {
final Long value = Long.valueOf( integralText );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( Long.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e2) {
try {
final BigInteger value = new BigInteger( integralText );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( BigInteger.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e3) {
final LiteralNumberFormatException exception = new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Integer, Long or BigInteger",
e
);
exception.addSuppressed( e );
exception.addSuppressed( e2 );
exception.addSuppressed( e3 );
throw exception;
}
}
}
private SqmHqlNumericLiteral<Integer> integerLiteral(String text) {
return new SqmHqlNumericLiteral<>(
text.replace( "_", "" ),
integerDomainType,
creationContext.getNodeBuilder()
);
}
private SqmLiteral<Long> longLiteral(String text) {
final String originalText = text;
try {
if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) {
text = text.substring( 0, text.length() - 1 ).replace("_", "");
}
final Long value = Long.valueOf( text );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( Long.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "] to Long",
e
);
}
private SqmHqlNumericLiteral<Long> longLiteral(String text) {
assert text.endsWith( "l" ) || text.endsWith( "L" );
return new SqmHqlNumericLiteral<>(
text.substring( 0, text.length() - 1 ).replace( "_", "" ),
resolveExpressibleTypeBasic( Long.class ),
creationContext.getNodeBuilder()
);
}
private SqmLiteral<? extends Number> hexLiteral(String text) {
final String originalText = text;
text = text.substring( 2 );
try {
if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) {
text = text.substring( 0, text.length() - 1 );
final long value = Long.parseUnsignedLong( text, 16 );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( Long.class ),
creationContext.getNodeBuilder()
);
}
else {
final int value = Integer.parseUnsignedInt( text, 16 );
return new SqmLiteral<>(
value,
resolveExpressibleTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
);
}
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "]",
e
);
}
private SqmHqlNumericLiteral<BigInteger> bigIntegerLiteral(String text) {
assert text.endsWith( "bi" ) || text.endsWith( "BI" );
return new SqmHqlNumericLiteral<>(
text.substring( 0, text.length() - 2 ).replace( "_", "" ),
resolveExpressibleTypeBasic( BigInteger.class ),
creationContext.getNodeBuilder()
);
}
private SqmLiteral<BigInteger> bigIntegerLiteral(String text) {
final String originalText = text;
try {
if ( text.endsWith( "bi" ) || text.endsWith( "BI" ) ) {
text = text.substring( 0, text.length() - 2 );
}
return new SqmLiteral<>(
new BigInteger( text ),
resolveExpressibleTypeBasic( BigInteger.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "] to BigInteger",
e
);
}
private SqmHqlNumericLiteral<? extends Number> floatLiteral(String text) {
assert text.endsWith( "f" ) || text.endsWith( "F" );
return new SqmHqlNumericLiteral<>(
text.substring( 0, text.length() - 1 ).replace( "_", "" ),
resolveExpressibleTypeBasic( Float.class ),
creationContext.getNodeBuilder()
);
}
private SqmLiteral<? extends Number> numericLiteral(String text) {
final String numericText = text.replace( "_", "" );
final char lastChar = text.charAt( text.length() - 1 );
if ( lastChar == 'f' || lastChar == 'F' ) {
try {
return new SqmLiteral<>(
Float.valueOf( numericText ),
resolveExpressibleTypeBasic( Float.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Float",
e
);
}
private SqmHqlNumericLiteral<Double> doubleLiteral(String text) {
if ( text.endsWith( "d" ) || text.endsWith( "D" ) ) {
text = text.substring( 0, text.length() - 1 );
}
return new SqmHqlNumericLiteral<>(
text.replace( "_", "" ),
resolveExpressibleTypeBasic( Double.class ),
creationContext.getNodeBuilder()
);
}
private SqmHqlNumericLiteral<BigDecimal> bigDecimalLiteral(String text) {
assert text.endsWith( "bd" ) || text.endsWith( "BD" );
return new SqmHqlNumericLiteral<>(
text.substring( 0, text.length() - 2 ).replace( "_", "" ),
resolveExpressibleTypeBasic( BigDecimal.class ),
creationContext.getNodeBuilder()
);
}
private SqmHqlNumericLiteral<? extends Number> hexLiteral(String text) {
if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) {
return longLiteral( text );
}
else {
// Treat numeric literals without a type suffix like BigDecimal
// according to the JPA spec 4.6.1 and SQL spec 5.3
try {
return new SqmLiteral<>(
new BigDecimal( numericText ),
resolveExpressibleTypeBasic( BigDecimal.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to BigDecimal",
e
);
}
}
}
private SqmLiteral<Double> doubleLiteral(String text) {
try {
return new SqmLiteral<>(
Double.valueOf( text ),
resolveExpressibleTypeBasic( Double.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Double",
e
);
}
}
private SqmLiteral<BigDecimal> bigDecimalLiteral(String text) {
final String originalText = text;
try {
if ( text.endsWith( "bd" ) || text.endsWith( "BD" ) ) {
text = text.substring( 0, text.length() - 2 );
}
return new SqmLiteral<>(
new BigDecimal( text ),
resolveExpressibleTypeBasic( BigDecimal.class ),
creationContext.getNodeBuilder()
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "] to BigDecimal",
e
);
return integerLiteral( text );
}
}

View File

@ -45,6 +45,7 @@ import org.hibernate.query.TypedParameterValue;
import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.internal.QueryOptionsImpl;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@ -132,6 +133,10 @@ public abstract class AbstractCommonQueryContract implements CommonQueryContract
if ( expression instanceof SqmLiteral<?> ) {
fetchValue = ( (SqmLiteral<Number>) expression ).getLiteralValue();
}
else if ( expression instanceof SqmHqlNumericLiteral ) {
final SqmHqlNumericLiteral<Number> hqlNumericLiteral = (SqmHqlNumericLiteral<Number>) expression;
fetchValue = hqlNumericLiteral.getTypeCategory().parseLiteralValue( hqlNumericLiteral.getLiteralValue() );
}
else if ( expression instanceof SqmParameter<?> ) {
fetchValue = getParameterValue( (Parameter<Number>) expression );
if ( fetchValue == null ) {

View File

@ -64,6 +64,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
@ -283,6 +284,8 @@ public interface SemanticQueryWalker<T> {
T visitFieldLiteral(SqmFieldLiteral<?> sqmFieldLiteral);
<N extends Number> T visitHqlNumericLiteral(SqmHqlNumericLiteral<N> numericLiteral);
T visitTuple(SqmTuple<?> sqmTuple);
T visitCollation(SqmCollation sqmCollate);

View File

@ -53,6 +53,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
@ -1140,6 +1141,11 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
return null;
}
@Override
public <N extends Number> Object visitHqlNumericLiteral(SqmHqlNumericLiteral<N> numericLiteral) {
return null;
}
@Override
public Object visitFullyQualifiedClass(Class namedClass) {
return null;

View File

@ -54,6 +54,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
@ -837,6 +838,11 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
return literal;
}
@Override
public <N extends Number> Object visitHqlNumericLiteral(SqmHqlNumericLiteral<N> numericLiteral) {
return numericLiteral;
}
@Override
public Object visitTuple(SqmTuple<?> sqmTuple) {
sqmTuple.getGroupedExpressions().forEach( e -> e.accept( this ) );

View File

@ -216,6 +216,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
@ -338,6 +339,7 @@ import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.CorrelatedPluralTableGroup;
import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup;
import org.hibernate.sql.ast.tree.from.FromClause;
@ -5506,6 +5508,64 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
@Override
public <N extends Number> Expression visitHqlNumericLiteral(SqmHqlNumericLiteral<N> numericLiteral) {
final BasicValuedMapping inferredExpressible = (BasicValuedMapping) getInferredValueMapping();
final BasicValuedMapping expressible;
if ( inferredExpressible == null ) {
expressible = (BasicValuedMapping) determineCurrentExpressible( numericLiteral );
}
else {
expressible = inferredExpressible;
}
final JdbcMapping jdbcMapping = expressible.getJdbcMapping();
if ( jdbcMapping.getValueConverter() != null ) {
// special case where we need to parse the value in order to apply the conversion
return handleConvertedUnparsedNumericLiteral( numericLiteral, expressible );
}
return new UnparsedNumericLiteral<>( numericLiteral.getLiteralValue(), jdbcMapping );
}
private <N extends Number> Expression handleConvertedUnparsedNumericLiteral(
SqmHqlNumericLiteral<N> numericLiteral,
BasicValuedMapping expressible) {
//noinspection rawtypes
final BasicValueConverter valueConverter = expressible.getJdbcMapping().getValueConverter();
assert valueConverter != null;
final Number parsedValue = numericLiteral.getTypeCategory().parseLiteralValue( numericLiteral.getLiteralValue() );
final Object sqlLiteralValue;
if ( valueConverter.getDomainJavaType().isInstance( parsedValue ) ) {
//noinspection unchecked
sqlLiteralValue = valueConverter.toRelationalValue( parsedValue );
}
else if ( valueConverter.getRelationalJavaType().isInstance( parsedValue ) ) {
sqlLiteralValue = parsedValue;
}
else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) ) {
//noinspection unchecked
sqlLiteralValue = valueConverter.getRelationalJavaType().coerce(
parsedValue,
creationContext.getSessionFactory()::getTypeConfiguration
);
}
else {
throw new SemanticException(
String.format(
Locale.ROOT,
"Literal type '%s' did not match domain type '%s' nor converted type '%s'",
parsedValue.getClass(),
valueConverter.getDomainJavaType().getJavaTypeClass().getName(),
valueConverter.getRelationalJavaType().getJavaTypeClass().getName()
)
);
}
return new QueryLiteral<>( sqlLiteralValue, expressible );
}
private MappingModelExpressible<?> getKeyExpressible(JdbcMappingContainer mappingModelExpressible) {
if ( mappingModelExpressible instanceof EntityAssociationMapping ) {
return ( (EntityAssociationMapping) mappingModelExpressible ).getKeyTargetMatchPart();

View File

@ -0,0 +1,62 @@
/*
* 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.query.sqm.tree.expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
/**
* @author Steve Ebersole
*/
public enum NumericTypeCategory {
INTEGER,
LONG,
BIG_INTEGER,
DOUBLE,
FLOAT,
BIG_DECIMAL;
public <N extends Number> N parseLiteralValue(String value) {
switch ( this ) {
case INTEGER: {
//noinspection unchecked
return (N) Integer.valueOf( value );
}
case LONG: {
//noinspection unchecked
return (N) Long.valueOf( value );
}
case BIG_INTEGER: {
//noinspection unchecked
return (N) new BigInteger( value );
}
case DOUBLE: {
//noinspection unchecked
return (N) Double.valueOf( value );
}
case FLOAT: {
//noinspection unchecked
return (N) Float.valueOf( value );
}
case BIG_DECIMAL: {
//noinspection unchecked
return (N) new BigDecimal( value );
}
default: {
throw new IllegalStateException(
String.format(
Locale.ROOT,
"Unable to parse numeric literal value `%s` - %s",
value,
name()
)
);
}
}
}
}

View File

@ -0,0 +1,165 @@
/*
* 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.query.sqm.tree.expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
import org.hibernate.HibernateException;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Used to model numeric literals found in HQL queries.
* <p/>
* Used instead of {@link SqmLiteral} which would require parsing the
* literal value to the specified number type to avoid loss of precision
* due to Float and Double being non-exact types.
*
* @apiNote Only used for HQL literals because we do not have this problem
* with criteria queries where the value given us by user would already be
* typed.
*
* @author Steve Ebersole
*/
public class SqmHqlNumericLiteral<N extends Number> extends AbstractSqmExpression<N> {
private final String literalValue;
private final NumericTypeCategory typeCategory;
public SqmHqlNumericLiteral(
String literalValue,
BasicDomainType<N> type,
NodeBuilder criteriaBuilder) {
this( literalValue, interpretCategory( literalValue, type ), type, criteriaBuilder );
}
public SqmHqlNumericLiteral(
String literalValue,
NumericTypeCategory typeCategory,
BasicDomainType<N> type,
NodeBuilder criteriaBuilder) {
super( type, criteriaBuilder );
this.literalValue = literalValue;
this.typeCategory = typeCategory;
}
public String getLiteralValue() {
return literalValue;
}
public NumericTypeCategory getTypeCategory() {
return typeCategory;
}
@Override
public BasicDomainType<N> getNodeType() {
return (BasicDomainType<N>) super.getNodeType();
}
@Override
public BasicDomainType<N> getExpressible() {
return getNodeType();
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitHqlNumericLiteral( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( literalValue );
switch ( typeCategory ) {
case BIG_DECIMAL: {
sb.append( "bd" );
break;
}
case FLOAT: {
sb.append( "f" );
break;
}
case BIG_INTEGER: {
sb.append( "bi" );
break;
}
case LONG: {
sb.append( "l" );
break;
}
default: {
// nothing to do for double/integer
}
}
}
@Override
public String asLoggableText() {
final StringBuilder stringBuilder = new StringBuilder();
appendHqlString( stringBuilder );
return stringBuilder.toString();
}
@Override
public SqmHqlNumericLiteral<N> copy(SqmCopyContext context) {
return new SqmHqlNumericLiteral<>( literalValue, typeCategory, getExpressible(), nodeBuilder() );
}
private static <N extends Number> NumericTypeCategory interpretCategory(String literalValue, SqmExpressible<N> type) {
assert type != null;
final JavaType<N> javaTypeDescriptor = type.getExpressibleJavaType();
assert javaTypeDescriptor != null;
final Class<N> javaTypeClass = javaTypeDescriptor.getJavaTypeClass();
if ( BigDecimal.class.equals( javaTypeClass ) ) {
return NumericTypeCategory.BIG_DECIMAL;
}
if ( Double.class.equals( javaTypeClass ) ) {
return NumericTypeCategory.DOUBLE;
}
if ( Float.class.equals( javaTypeClass ) ) {
return NumericTypeCategory.FLOAT;
}
if ( BigInteger.class.equals( javaTypeClass ) ) {
return NumericTypeCategory.BIG_INTEGER;
}
if ( Long.class.equals( javaTypeClass ) ) {
return NumericTypeCategory.LONG;
}
if ( Short.class.equals( javaTypeClass )
|| Integer.class.equals( javaTypeClass ) ) {
return NumericTypeCategory.INTEGER;
}
throw new TypeException( literalValue, javaTypeClass );
}
public static class TypeException extends HibernateException {
public TypeException(String literalValue, Class<?> javaType) {
super(
String.format(
Locale.ROOT,
"Unexpected Java type [%s] for numeric literal - %s",
javaType.getTypeName(),
literalValue
)
);
}
}
}

View File

@ -39,6 +39,7 @@ import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
@ -173,6 +174,8 @@ public interface SqlAstWalker {
void visitQueryLiteral(QueryLiteral<?> queryLiteral);
<N extends Number> void visitUnparsedNumericLiteral(UnparsedNumericLiteral<N> literal);
void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression);
void visitModifiedSubQueryExpression(ModifiedSubQueryExpression expression);

View File

@ -133,6 +133,7 @@ import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.DerivedTableReference;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
@ -6675,6 +6676,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
visitLiteral( queryLiteral );
}
@Override
public <N extends Number> void visitUnparsedNumericLiteral(UnparsedNumericLiteral<N> literal) {
appendSql( literal.getLiteralValue() );
}
private void visitLiteral(Literal literal) {
if ( literal.getLiteralValue() == null ) {
renderNull( literal );

View File

@ -45,6 +45,7 @@ import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
@ -515,6 +516,10 @@ public class AbstractSqlAstWalker implements SqlAstWalker {
public void visitQueryLiteral(QueryLiteral<?> queryLiteral) {
}
@Override
public <N extends Number> void visitUnparsedNumericLiteral(UnparsedNumericLiteral<N> literal) {
}
@Override
public void visitEntityTypeLiteral(EntityTypeLiteral expression) {
}

View File

@ -44,6 +44,7 @@ import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
@ -233,6 +234,11 @@ public class ExpressionReplacementWalker implements SqlAstWalker {
doReplaceExpression( queryLiteral );
}
@Override
public <N extends Number> void visitUnparsedNumericLiteral(UnparsedNumericLiteral<N> literal) {
doReplaceExpression( literal );
}
@Override
public void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression) {
doReplaceExpression( unaryOperationExpression );

View File

@ -0,0 +1,82 @@
/*
* 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.sql.ast.tree.expression;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.spi.TypeConfiguration;
/**
* A numeric literal coming from an HQL query, which needs special handling
*
* @see org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral
*
* @author Steve Ebersole
*/
public class UnparsedNumericLiteral<N extends Number> implements Expression, DomainResultProducer<N> {
private final String literalValue;
private final JdbcMapping jdbcMapping;
public UnparsedNumericLiteral(String literalValue, JdbcMapping jdbcMapping) {
this.literalValue = literalValue;
this.jdbcMapping = jdbcMapping;
}
public String getLiteralValue() {
return literalValue;
}
public JdbcMapping getJdbcMapping() {
return jdbcMapping;
}
@Override
public JdbcMappingContainer getExpressionType() {
return getJdbcMapping();
}
@Override
public DomainResult<N> createDomainResult(String resultVariable, DomainResultCreationState creationState) {
final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TypeConfiguration typeConfiguration = creationState.getSqlAstCreationState().getCreationContext().getSessionFactory().getTypeConfiguration();
final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection(
this,
getJdbcMapping().getJdbcJavaType(),
null,
typeConfiguration
);
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
resultVariable,
jdbcMapping
);
}
@Override
public void applySqlSelections(DomainResultCreationState creationState) {
creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
this,
jdbcMapping.getJdbcJavaType(),
null,
creationState.getSqlAstCreationState().getCreationContext().getMappingMetamodel().getTypeConfiguration()
);
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.visitUnparsedNumericLiteral( this );
}
}

View File

@ -1,52 +0,0 @@
package org.hibernate.orm.test.query;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
/**
* @author Andrias Sundskar
* @author Nathan Xu
*/
@TestForIssue( jiraKey = "HHH-14213" )
public class IntegerRepresentationLiteralParsingExceptionTest extends BaseEntityManagerFunctionalTestCase {
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { ExampleEntity.class };
}
@Test
public void testAppropriateExceptionMessageGenerated() {
try {
doInJPA( this::entityManagerFactory, entityManager -> {
// 9223372036854775808 is beyond Long range, so an Exception will be thrown
entityManager.createQuery( "select count(*) from ExampleEntity where counter = 9223372036854775808L" )
.getSingleResult();
} );
Assert.fail( "Exception should be thrown" );
}
catch (Exception e) {
// without fixing HHH-14213, the following exception would be thrown:
// "Could not parse literal [9223372036854775808L] as integer"
// which is confusing and misleading
Assert.assertTrue( e.getMessage().endsWith( " to Long" ) );
}
}
@Entity(name = "ExampleEntity")
static class ExampleEntity {
@Id
int id;
long counter;
}
}

View File

@ -12,6 +12,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@ -57,7 +58,7 @@ public class CaseExpressionsTest extends BaseSqmUnitTest {
assertThat( caseStatement.getFixture(), instanceOf( SqmPath.class ) );
assertThat( caseStatement.getOtherwise(), notNullValue() );
assertThat( caseStatement.getOtherwise(), instanceOf( SqmLiteral.class ) );
assertThat( caseStatement.getOtherwise(), instanceOf( SqmHqlNumericLiteral.class ) );
assertThat( caseStatement.getWhenFragments().size(), is(1) );
}
@ -79,7 +80,7 @@ public class CaseExpressionsTest extends BaseSqmUnitTest {
);
assertThat( caseStatement.getOtherwise(), notNullValue() );
assertThat( caseStatement.getOtherwise(), instanceOf( SqmLiteral.class ) );
assertThat( caseStatement.getOtherwise(), instanceOf( SqmHqlNumericLiteral.class ) );
assertThat( caseStatement.getWhenFragments().size(), is(1) );
}

View File

@ -11,6 +11,7 @@ import org.hibernate.orm.test.query.sqm.domain.Person;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmCollectionSize;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.predicate.SqmNullnessPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
@ -80,8 +81,8 @@ public class WhereClauseTests extends BaseSqmUnitTest {
assertThat( relationalPredicate.getSqmOperator(), is( ComparisonOperator.EQUAL ) );
assertThat( relationalPredicate.getRightHandExpression(), instanceOf( SqmLiteral.class ) );
assertThat( ( (SqmLiteral<?>) relationalPredicate.getRightHandExpression() ).getLiteralValue(), is( 311 ) );
assertThat( relationalPredicate.getRightHandExpression(), instanceOf( SqmHqlNumericLiteral.class ) );
assertThat( ( (SqmHqlNumericLiteral<?>) relationalPredicate.getRightHandExpression() ).getLiteralValue(), is( "311" ) );
assertThat( relationalPredicate.getLeftHandExpression(), instanceOf( SqmCollectionSize.class ) );
@ -100,8 +101,8 @@ public class WhereClauseTests extends BaseSqmUnitTest {
assertThat( relationalPredicate.getSqmOperator(), is( ComparisonOperator.GREATER_THAN ) );
assertThat( relationalPredicate.getRightHandExpression(), instanceOf( SqmLiteral.class ) );
assertThat( ( (SqmLiteral<?>) relationalPredicate.getRightHandExpression() ).getLiteralValue(), is( 2 ) );
assertThat( relationalPredicate.getRightHandExpression(), instanceOf( SqmHqlNumericLiteral.class ) );
assertThat( ( (SqmHqlNumericLiteral<?>) relationalPredicate.getRightHandExpression() ).getLiteralValue(), is( "2" ) );
assertThat( relationalPredicate.getLeftHandExpression(), instanceOf( SqmPath.class ) );
final SqmPath<?> indexPath = (SqmPath<?>) relationalPredicate.getLeftHandExpression();

View File

@ -22,7 +22,7 @@ import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolv
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.hibernate.testing.orm.junit.RequiresDialect;
@ -61,7 +61,7 @@ public class SubqueryTest extends BaseSessionFactoryFunctionalTest {
public void render(
SqlAppender sqlAppender, List<? extends SqlAstNode> sqlAstArguments, SqlAstTranslator<?> walker) {
sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.appendSql( " limit " + ( (QueryLiteral<?>) sqlAstArguments.get( 1 ) ).getLiteralValue() );
sqlAppender.appendSql( " limit " + ( (UnparsedNumericLiteral<?>) sqlAstArguments.get( 1 ) ).getLiteralValue() );
}
}

View File

@ -14,6 +14,26 @@ earlier versions, see any other pertinent migration guides as well.
* link:{docsBase}/6.1/migration-guide/migration-guide.html[6.1 Migration guide]
* link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide]
[[hql-numeric-literal-types]]
== HQL Numeric Literal Types
Version 3.2 of the Jakarta Persistence specification
https://github.com/jakartaee/persistence/issues/423[clarifies] the interpretation of
numeric literals with regard to type, explicitly aligning with the Java specification (as well
as adopting Hibernate's long-standing `BigInteger` and `BigDecimal` suffixes).
HQL and JPQL are domain/object-level queries, so that makes perfect sense.
* `Integer` - 123
* `Long` - 123l, 123L
* `BigInteger` - 123bi, 123BI
* `Double` - 123.4
* `Float` - 123.4f, 123.4F
* `BigDecimal` - 123.4bd, 123.4BD
Hibernate 6.3 aligns with those interpretations, which may lead to different behavior
from prior versions.
[[batch-fetching-changes]]
== Batch Fetching and LockMode
@ -22,8 +42,9 @@ This because the lock mode is different from the one of the proxies in the batch
E.g.
`
```java
MyEntity proxy = session.getReference( MyEntity.class, 1 );
MyEntity myEntity = session.find(MyEntity.class, 2, LockMode.WRITE);
`
```
only the entity with id equals to 2 will be loaded but the proxy will not be initialized.