blob: 20701435a5717f896c1a12ca788704718fe56fb2 [file] [log] [blame]
/*
* 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);
}
}
}