| /* |
| * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // 02/01/2022: Tomas Kraus |
| // - Issue 1442: Implement New JPA API 3.1.0 Features |
| package org.eclipse.persistence.jpa.test.criteria; |
| |
| import java.math.RoundingMode; |
| import java.text.DecimalFormat; |
| |
| import jakarta.persistence.EntityManager; |
| import jakarta.persistence.EntityManagerFactory; |
| import jakarta.persistence.PersistenceException; |
| import jakarta.persistence.criteria.CriteriaBuilder; |
| import jakarta.persistence.criteria.CriteriaQuery; |
| import jakarta.persistence.criteria.Root; |
| |
| import org.eclipse.persistence.jpa.test.criteria.model.NumberEntity; |
| import org.eclipse.persistence.jpa.test.framework.DDLGen; |
| import org.eclipse.persistence.jpa.test.framework.Emf; |
| import org.eclipse.persistence.jpa.test.framework.EmfRunner; |
| import org.eclipse.persistence.jpa.test.framework.Property; |
| import org.eclipse.persistence.sessions.Session; |
| import org.hamcrest.MatcherAssert; |
| import org.hamcrest.Matchers; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| /** |
| * Test math functions in CriteriaBuilder. |
| * Added to JPA-API as PR #351 |
| */ |
| @RunWith(EmfRunner.class) |
| public class TestMathFunctions { |
| |
| @Emf(createTables = DDLGen.DROP_CREATE, |
| classes = { |
| NumberEntity.class |
| }, |
| properties = { |
| @Property(name = "eclipselink.cache.shared.default", value = "false"), |
| // This property remaps String from VARCHAR->NVARCHAR(or db equivalent) |
| @Property(name = "eclipselink.target-database-properties", |
| value = "UseNationalCharacterVaryingTypeForString=true"), |
| }) |
| private EntityManagerFactory emf; |
| |
| private final NumberEntity[] NUMBER = { |
| new NumberEntity(0, 0L, 0D), |
| new NumberEntity(1, 1L, 1D), |
| new NumberEntity(2, -1L, -1D), |
| new NumberEntity(3, 42L, 42.42D), |
| new NumberEntity(4, -342L, -342.42D), |
| new NumberEntity(5, 4L, 4D), |
| new NumberEntity(6, -4L, -4D), |
| new NumberEntity(7, 4L, 14.23D), |
| new NumberEntity(8, 6L, 44.7542383252D), |
| new NumberEntity(9, 8L, -214.2457321233D) |
| }; |
| |
| @Before |
| public void setup() { |
| final EntityManager em = emf.createEntityManager(); |
| try { |
| em.getTransaction().begin(); |
| for (NumberEntity e : NUMBER) { |
| em.persist(e); |
| } |
| em.flush(); |
| em.getTransaction().commit(); |
| } finally { |
| if (em.getTransaction().isActive()) { |
| em.getTransaction().rollback(); |
| } |
| em.close(); |
| } |
| } |
| |
| @After |
| public void cleanup() { |
| final EntityManager em = emf.createEntityManager(); |
| try { |
| em.getTransaction().begin(); |
| em.createQuery("DELETE FROM NumberEntity e").executeUpdate(); |
| em.flush(); |
| em.getTransaction().commit(); |
| } finally { |
| if (em.getTransaction().isActive()) { |
| em.getTransaction().rollback(); |
| } |
| em.close(); |
| } |
| } |
| |
| // Verify result rounded to 10 decimal places as String operation. |
| // Precision of Java and relational DB floating point operation may not be the same. |
| private static void verifyRoundedDouble(final double expected, final double returned) { |
| // Round values down to 10 decimal places |
| DecimalFormat df = new DecimalFormat("#.##########"); |
| df.setRoundingMode(RoundingMode.DOWN); |
| String expectedRounded = df.format(expected); |
| String returnedRounded = df.format(returned); |
| Assert.assertEquals(expectedRounded, returnedRounded); |
| } |
| |
| // Call SELECT SIGN(n.longValue) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| private static Integer callSign(final EntityManager em, final int id) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Integer> cq = cb.createQuery(Integer.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.sign(number.get("longValue"))); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call SIGN(n) on n=0. |
| @Test |
| public void testSignMethodWithZero() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Integer result = callSign(em, 0); |
| Assert.assertEquals(Integer.valueOf(0), result); |
| } |
| } |
| |
| // Call SIGN(n) on n>0. |
| @Test |
| public void testSignMethodWithPositive() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Integer result = callSign(em, 3); |
| Assert.assertEquals(Integer.valueOf(1), result); |
| } |
| } |
| |
| // Call SIGN(n) on n<0. |
| @Test |
| public void testSignMethodWithNegative() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Integer result = callSign(em, 4); |
| Assert.assertEquals(Integer.valueOf(-1), result); |
| } |
| } |
| |
| // Call SELECT CEILING(n.doubleValue) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| private static Double callCeiling(final EntityManager em, final int id) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.ceiling(number.get("doubleValue"))); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call CEILING(n) on n=0. |
| @Test |
| public void testCeilingMethodWithZero() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callCeiling(em, 0); |
| Assert.assertEquals(Double.valueOf(0), result); |
| } |
| } |
| |
| // Call CEILING(n) on n>0. |
| @Test |
| public void testCeilingMethodWithPositive() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callCeiling(em, 3); |
| Assert.assertEquals( |
| Double.valueOf(NUMBER[3].getLongValue().intValue()+1), result); |
| } |
| } |
| |
| // Call CEILING(n) on n<0. |
| @Test |
| public void testCeilingMethodWithNegative() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callCeiling(em, 4); |
| Assert.assertEquals( |
| Double.valueOf(NUMBER[4].getLongValue().intValue()), result); |
| } |
| } |
| |
| // Call SELECT FLOOR(n.doubleValue) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| private static Double callFloor(final EntityManager em, final int id) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.floor(number.get("doubleValue"))); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call FLOOR(n) on n=0. |
| @Test |
| public void testFloorMethodWithZero() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callFloor(em, 0); |
| Assert.assertEquals(Double.valueOf(0), result); |
| } |
| } |
| |
| // Call FLOOR(n) on n>0. |
| @Test |
| public void testFloorMethodWithPositive() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callFloor(em, 3); |
| Assert.assertEquals( |
| Double.valueOf(NUMBER[3].getLongValue().intValue()), result); |
| } |
| } |
| |
| // Call FLOOR(n) on n<0. |
| @Test |
| public void testFloorMethodWithNegative() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callFloor(em, 4); |
| Assert.assertEquals( |
| Double.valueOf(NUMBER[4].getLongValue().intValue()-1), result); |
| } |
| } |
| |
| // Call SELECT EXP(n.doubleValue) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| private static Double callExp(final EntityManager em, final int id) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.exp(number.get("doubleValue"))); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call EXP(0). |
| @Test |
| public void testExpMethodWithZero() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callExp(em, 0); |
| Assert.assertEquals( |
| Double.valueOf(Math.exp(NUMBER[0].getDoubleValue())), result); |
| } |
| } |
| |
| // Call EXP(1). Result is rounded down to 10 decimal places. |
| @Test |
| public void testExpMethodWithPlusOne() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| verifyRoundedDouble(Math.exp(NUMBER[1].getDoubleValue()), callExp(em, 1)); |
| } |
| } |
| |
| // Call EXP(-1). Result is rounded down to 10 decimal places. |
| @Test |
| public void testExpMethodWithMinusOne() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| verifyRoundedDouble(Math.exp(NUMBER[2].getDoubleValue()), callExp(em, 2)); |
| } |
| } |
| |
| // Call EXP(n) on n>0. Result is rounded down to 10 decimal places. |
| @Test |
| public void testExpMethodWithPositive() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| verifyRoundedDouble(Math.exp(NUMBER[5].getDoubleValue()), callExp(em, 5)); |
| } |
| } |
| |
| // Call EXP(n) on n<0. Result is rounded down to 10 decimal places. |
| @Test |
| public void testExpMethodWithNegative() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| verifyRoundedDouble(Math.exp(NUMBER[6].getDoubleValue()), callExp(em, 6)); |
| } |
| } |
| |
| // Call SELECT LN(n.doubleValue) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| private static Double callLn(final EntityManager em, final int id) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.ln(number.get("doubleValue"))); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call LN(0). Domain of f(x): y = ln(x) is (0,infinity) so it shall throw an exception or return null. |
| @Test |
| public void testLnMethodWithZero() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callLn(em, 0); |
| Assert.assertNull(result); |
| } catch (PersistenceException pe) { |
| // Expected to be thrown |
| } |
| } |
| |
| // Call LN(1). Result is rounded down to 10 decimal places. |
| @Test |
| public void testLnMethodWithPlusOne() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| verifyRoundedDouble(Math.log(NUMBER[1].getDoubleValue()), callLn(em, 1)); |
| } |
| } |
| |
| // Call LN(-1). Domain of f(x): y = ln(x) is (0,infinity) so it shall throw an exception or return null. |
| @Test |
| public void testLnMethodWithMinusOne() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callLn(em, 2); |
| Assert.assertNull(result); |
| } catch (PersistenceException pe) { |
| // Expected to be thrown |
| } |
| } |
| |
| // Call LN(n) on n>0. Result is rounded down to 10 decimal places. |
| @Test |
| public void testLnMethodWithPositive() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| verifyRoundedDouble(Math.log(NUMBER[5].getDoubleValue()), callLn(em, 5)); |
| } |
| } |
| |
| // Call LN(n) on n<0. Domain of f(x): y = ln(x) is (0,infinity) so it shall throw an exception or return null. |
| @Test |
| public void testLnMethodWithNegative() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callLn(em, 6); |
| Assert.assertNull(result); |
| } catch (PersistenceException pe) { |
| // Expected to be thrown |
| } |
| } |
| |
| // Call SELECT POWER(n.:field, exponent) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| // Matches Expression<Double> power(Expression<? extends Number> x, Number y) prototype |
| private static Double callPower(final EntityManager em, final int exponent, final int id, final String field) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.power(number.get(field), exponent)); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call POWER(n.longValue, 2) on long n=0. |
| @Test |
| public void testPower2MethodWithZeroBase() { |
| // org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type DOUBLE. |
| Assume.assumeFalse(emf.unwrap(Session.class).getPlatform().isDerby()); |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 2, 0, "longValue"); |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[0].getLongValue(), 2)), result); |
| } |
| } |
| |
| // Call POWER(n.longValue, 2) on long n>0. |
| @Test |
| public void testPower2MethodWithPositiveLongBase() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 2, 3, "longValue"); |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[3].getLongValue(), 2)), Double.valueOf(Math.round(result))); |
| } |
| } |
| |
| // Call POWER(n.doubleValue, 2) on double n>0. |
| @Test |
| public void testPower2MethodWithPositiveDoubleBase() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 2, 3, "doubleValue"); |
| if (emf.unwrap(Session.class).getPlatform().isDerby()) { |
| MatcherAssert.assertThat( |
| Math.abs(Math.pow(NUMBER[3].getDoubleValue(), 2) - result), Matchers.lessThan(0.0000001d)); |
| } else { |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[3].getDoubleValue(), 2)), result); |
| } |
| } |
| } |
| |
| // Call POWER(n.longValue, 2) on long n<0. |
| @Test |
| public void testPower2MethodWithNegativeLongBase() { |
| // org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type DOUBLE. |
| Assume.assumeFalse(emf.unwrap(Session.class).getPlatform().isDerby()); |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 2, 4, "longValue"); |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[4].getLongValue(), 2)), result); |
| } |
| } |
| |
| // Call POWER(n.doubleValue, 2) on double n<0. |
| @Test |
| public void testPower2MethodWithNegativeDoubleBase() { |
| // org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type DOUBLE. |
| Assume.assumeFalse(emf.unwrap(Session.class).getPlatform().isDerby()); |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 2, 4, "doubleValue"); |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[4].getDoubleValue(), 2)), result); |
| } |
| } |
| |
| // Call POWER(n.longValue, 3) on long n<0. |
| @Test |
| public void testPower3MethodWithNegativeLongBase() { |
| // org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type DOUBLE. |
| Assume.assumeFalse(emf.unwrap(Session.class).getPlatform().isDerby()); |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 3, 4, "longValue"); |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[4].getLongValue(), 3)), result); |
| } |
| } |
| |
| // Call POWER(n.doubleValue, 3) on double n<0. |
| @Test |
| public void testPower3MethodWithNegativeDoubleBase() { |
| // org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type DOUBLE. |
| Assume.assumeFalse(emf.unwrap(Session.class).getPlatform().isDerby()); |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callPower(em, 3, 4, "doubleValue"); |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[4].getDoubleValue(), 3)), result); |
| } |
| } |
| |
| // Call SELECT POWER(n.doubleValue, n.longValue) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| // Matches Expression<Double> power(Expression<? extends Number> x, Expression<? extends Number> y) prototype |
| private static Double callExprPower(final EntityManager em, final int id) { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.power(number.get("doubleValue"), number.get("longValue"))); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } |
| |
| // Call POWER(n.doubleValue, n.longValue) on id=7: [14.23D,4L] (result fits in double with no exponent). |
| @Test |
| public void testPowerMethodWithPositiveArgs() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callExprPower(em, 7); |
| if (emf.unwrap(Session.class).getPlatform().isDerby()) { |
| MatcherAssert.assertThat( |
| Math.abs(Math.pow(NUMBER[7].getDoubleValue(), NUMBER[7].getLongValue()) - result), |
| Matchers.lessThan(0.0000001d)); |
| } else { |
| Assert.assertEquals( |
| Double.valueOf(Math.pow(NUMBER[7].getDoubleValue(), NUMBER[7].getLongValue())), result); |
| } |
| } |
| } |
| |
| // Call SELECT ROUND(n.doubleValue, d) FROM NumberEntity n WHERE n.id = id |
| // using CriteriaQuery |
| // Matches Expression<Double> power(Expression<? extends Number> x, Number y) prototype |
| private static Double callRound(final EntityManager em, final int d, final int id) { |
| try { |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Double> cq = cb.createQuery(Double.class); |
| Root<NumberEntity> number = cq.from(NumberEntity.class); |
| cq.select(cb.round(number.get("doubleValue"), d)); |
| cq.where(cb.equal(number.get("id"), id)); |
| return em.createQuery(cq).getSingleResult(); |
| } catch (Throwable t) { |
| t.printStackTrace(); |
| throw t; |
| } |
| } |
| |
| // Call ROUND(n) on n>0. |
| @Test |
| public void testRoundMethodWithPositive() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callRound(em, 6,8); |
| Assert.assertEquals(Double.valueOf(44.754238D), result); |
| } |
| } |
| |
| // Call ROUND(n) on n<0. |
| @Test |
| public void testRoundMethodWithNegative() { |
| try (final EntityManager em = emf.createEntityManager()) { |
| Double result = callRound(em, 6, 9); |
| Assert.assertEquals(Double.valueOf(-214.245732D), result); |
| } |
| } |
| |
| } |