diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java index 04c9cae14e..aeb7c90373 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.function.Consumer; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.ComparisonOperator; import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; @@ -17,6 +18,7 @@ import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.Expression; @@ -267,4 +269,21 @@ public class DerbySqlAstTranslator extends AbstractSqlA return getDialect().getVersion().isSameOrAfter( 10, 5 ); } + @Override + public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) { + final BinaryArithmeticOperator operator = arithmeticExpression.getOperator(); + if ( operator == BinaryArithmeticOperator.MODULO ) { + append( "mod" ); + appendSql( OPEN_PARENTHESIS ); + arithmeticExpression.getLeftHandOperand().accept( this ); + appendSql( ',' ); + arithmeticExpression.getRightHandOperand().accept( this ); + appendSql( CLOSE_PARENTHESIS ); + return; + } + else { + super.visitBinaryArithmeticExpression( arithmeticExpression ); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java index b193b46aea..0692425b66 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java @@ -10,12 +10,14 @@ import java.util.List; import java.util.function.Consumer; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.ComparisonOperator; import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.Expression; @@ -217,4 +219,21 @@ public class HSQLSqlAstTranslator extends AbstractSqlAs private boolean supportsOffsetFetchClause() { return getDialect().getVersion().isSameOrAfter( 2, 5 ); } + + @Override + public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) { + final BinaryArithmeticOperator operator = arithmeticExpression.getOperator(); + if ( operator == BinaryArithmeticOperator.MODULO ) { + append( "mod" ); + appendSql( OPEN_PARENTHESIS ); + arithmeticExpression.getLeftHandOperand().accept( this ); + appendSql( ',' ); + arithmeticExpression.getRightHandOperand().accept( this ); + appendSql( CLOSE_PARENTHESIS ); + return; + } + else { + super.visitBinaryArithmeticExpression( arithmeticExpression ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java index 5a7e676218..9d9c6d539f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java @@ -10,6 +10,7 @@ import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.collections.Stack; +import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.FetchClauseType; import org.hibernate.query.IllegalQueryOperationException; @@ -17,6 +18,7 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.FunctionExpression; @@ -418,4 +420,21 @@ public class OracleSqlAstTranslator extends AbstractSql return getDialect().supportsFetchClause( FetchClauseType.ROWS_ONLY ); } + @Override + public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) { + final BinaryArithmeticOperator operator = arithmeticExpression.getOperator(); + if ( operator == BinaryArithmeticOperator.MODULO ) { + append( "mod" ); + appendSql( OPEN_PARENTHESIS ); + arithmeticExpression.getLeftHandOperand().accept( this ); + appendSql( ',' ); + arithmeticExpression.getRightHandOperand().accept( this ); + appendSql( CLOSE_PARENTHESIS ); + return; + } + else { + super.visitBinaryArithmeticExpression( arithmeticExpression ); + } + } + } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/ModulusTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/ModulusTest.java new file mode 100644 index 0000000000..d3ac471840 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/ModulusTest.java @@ -0,0 +1,132 @@ +package org.hibernate.orm.test.jpa.compliance; + +import java.util.List; + +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.Expression; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.metamodel.EntityType; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Jpa( + annotatedClasses = ModulusTest.Person.class +) +public class ModulusTest { + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + for ( int i = 0; i < 10; i++ ) { + Person person; + if ( i == 3 ) { + person = new Person( i, "Andrea", 5 ); + } + else if ( i == 4 ) { + person = new Person( i, "Andrew", 5 ); + } + else { + person = new Person( i, "Luigi " + i, 42 ); + } + entityManager.persist( person ); + } + } + ); + } + + @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 query = criteriaBuilder.createQuery( Integer.class ); + final Root person = query.from( Person.class ); + query.select( person.get( "id" ) ); + + final EntityType Person_ = entityManager.getMetamodel().entity( Person.class ); + + final Expression mod = criteriaBuilder.mod( + criteriaBuilder.literal( 45 ), + criteriaBuilder.literal( 10 ) + ); + final Path ageAttribute = person.get( Person_.getSingularAttribute( + "age", + Integer.class + ) ); + + query.where( criteriaBuilder.equal( mod, ageAttribute ) ); + + final List ids = entityManager.createQuery( query ).getResultList(); + + assertEquals( 2, ids.size() ); + assertTrue( ids.contains( 3 ) ); + assertTrue( ids.contains( 4 ) ); + } + ); + } + + @Test + public void testQueryMod(EntityManagerFactoryScope scope) { + scope.inEntityManager( + entityManager -> { + final List ids = entityManager.createQuery( "select p.id from Person p where p.age = 45%40" ) + .getResultList(); + + assertEquals( 2, ids.size() ); + assertTrue( ids.contains( 3 ) ); + assertTrue( ids.contains( 4 ) ); + } + ); + } + + @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; + } + } + +}