Fix expression type for arithmetic operations

This commit is contained in:
Andrea Boriero 2022-01-18 17:13:29 +01:00 committed by Christian Beikov
parent 4775be3bb8
commit 1e07c4f85f
4 changed files with 126 additions and 34 deletions

View File

@ -715,7 +715,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return createSqmArithmeticNode(
BinaryArithmeticOperator.ADD,
(SqmExpression<?>) x,
value( y, (SqmExpression) x )
value( y )
);
}
@ -724,7 +724,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
public <N extends Number> SqmExpression<N> sum(N x, Expression<? extends N> y) {
return createSqmArithmeticNode(
BinaryArithmeticOperator.ADD,
value( x, (SqmExpression) y ),
value( x ),
(SqmExpression<?>) y
);
}
@ -743,7 +743,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return createSqmArithmeticNode(
BinaryArithmeticOperator.MULTIPLY,
(SqmExpression<?>) x,
value( y, (SqmExpression<?>) x )
value( y )
);
}
@ -751,7 +751,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
public <N extends Number> SqmExpression<N> prod(N x, Expression<? extends N> y) {
return createSqmArithmeticNode(
BinaryArithmeticOperator.MULTIPLY,
value( x, (SqmExpression<?>) y ),
value( x ),
(SqmExpression<?>) y
);
}
@ -771,7 +771,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return createSqmArithmeticNode(
BinaryArithmeticOperator.SUBTRACT,
(SqmExpression) x,
value( y, (SqmExpression) x )
value( y )
);
}
@ -779,7 +779,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
public <N extends Number> SqmExpression<N> diff(N x, Expression<? extends N> y) {
return createSqmArithmeticNode(
BinaryArithmeticOperator.SUBTRACT,
value( x, (SqmExpression<?>) y ),
value( x ),
(SqmExpression<?>) y
);
}
@ -798,7 +798,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return createSqmArithmeticNode(
BinaryArithmeticOperator.QUOT,
(SqmExpression<?>) x,
value( y, (SqmExpression<?>) x )
value( y )
);
}
@ -807,7 +807,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
public SqmExpression<Number> quot(Number x, Expression<? extends Number> y) {
return createSqmArithmeticNode(
BinaryArithmeticOperator.QUOT,
value( x, (SqmExpression<? extends Number>) y ),
value( x ),
(SqmExpression<? extends Number>) y
);
}
@ -826,7 +826,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return createSqmArithmeticNode(
BinaryArithmeticOperator.MODULO,
(SqmExpression<Integer>) x,
value( y, (SqmExpression<Integer>) x )
value( y )
);
}
@ -834,7 +834,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
public SqmExpression<Integer> mod(Integer x, Expression<Integer> y) {
return createSqmArithmeticNode(
BinaryArithmeticOperator.MODULO,
value( x, (SqmExpression<Integer>) y ),
value( x ),
(SqmExpression<Integer>) y
);
}

View File

@ -4685,17 +4685,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
private BasicValuedMapping getExpressionType(SqmBinaryArithmetic<?> expression) {
final SqmExpressable<?> sqmExpressable = QueryHelper.highestPrecedenceType(
expression.getLeftHandOperand().getNodeType(),
expression.getRightHandOperand().getNodeType()
);
if ( sqmExpressable instanceof BasicValuedMapping ) {
return (BasicValuedMapping) sqmExpressable;
}
else if ( sqmExpressable != null ) {
return getTypeConfiguration().getBasicTypeForJavaType(
sqmExpressable.getExpressableJavaTypeDescriptor().getJavaTypeClass()
);
final SqmExpressable<?> nodeType = expression.getNodeType();
if ( nodeType != null ) {
if ( nodeType instanceof BasicValuedMapping ) {
return (BasicValuedMapping) nodeType;
}
else {
return getTypeConfiguration().getBasicTypeForJavaType(
nodeType.getExpressableJavaTypeDescriptor().getJavaTypeClass()
);
}
}
return JavaObjectType.INSTANCE;
}

View File

@ -511,19 +511,18 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
SqmExpressable<?> firstType,
SqmExpressable<?> secondType,
BinaryArithmeticOperator operator) {
return resolveArithmeticType( firstType, secondType, operator == DIVIDE );
return resolveArithmeticType( firstType, secondType );
}
/**
* Determine the result type of an arithmetic operation as defined by the
* rules in section 6.5.7.1.
* rules in section 6.5.8.1.
*
* @see QueryHelper#highestPrecedenceType2
*/
public SqmExpressable<?> resolveArithmeticType(
SqmExpressable<?> firstType,
SqmExpressable<?> secondType,
boolean isDivision) {
SqmExpressable<?> secondType) {
if ( getSqlTemporalType( firstType ) != null ) {
if ( secondType==null || getSqlTemporalType( secondType ) != null ) {
@ -548,15 +547,6 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return getBasicTypeRegistry().getRegisteredType( Duration.class );
}
if ( isDivision ) {
// covered under the note in 6.5.7.1 discussing the unportable
// "semantics of the SQL division operation"..
return getBasicTypeRegistry().getRegisteredType( Number.class.getName() );
}
// non-division
if ( matchesJavaType( firstType, Double.class ) ) {
return firstType;
}

View File

@ -0,0 +1,103 @@
/*
* 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.orm.test.jpa.compliance;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@Jpa(
annotatedClasses = ProdTest.Person.class
)
public class ProdTest {
@BeforeEach
public void setUp(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
entityManager.persist( new Person( 1, "Luigi ", 42 ) );
}
);
}
@AfterEach
public void tearDown(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager ->
entityManager.createQuery( "delete from Person" ).executeUpdate()
);
}
@Test
public void testCriteriaMod(EntityManagerFactoryScope scope) {
scope.inEntityManager(
entityManager -> {
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Number> query = criteriaBuilder.createQuery( Number.class );
final Root<Person> person = query.from( Person.class );
query.select( criteriaBuilder.prod( person.get( "age" ), 1F ) );
final Number id = entityManager.createQuery( query ).getSingleResult();
assertInstanceOf( Float.class, id );
assertEquals( 42F, id.floatValue() );
}
);
}
@Test
public void testQueryMod(EntityManagerFactoryScope scope) {
scope.inEntityManager(
entityManager -> {
final Object id = entityManager.createQuery( "select p.age * 1F from Person p" )
.getSingleResult();
assertInstanceOf( Float.class, id );
assertEquals( 42F, id );
}
);
}
@Entity(name = "Person")
@Table(name = "PERSON_TABLE")
public static class Person {
@Id
private Integer id;
private String name;
private Integer age;
Person() {
}
public Person(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
}
}