blob: 13a6b0d487ad5989e696b92cbd16659df24735ad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jira1794;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* OPENJPA-1794 Verifies the return value of aggregate functions when a query
* result set is empty. In this set of variations, the compatibility flag is not
* set so null is expected.
*/
public class TestAggregateFunctions extends SingleEMFTestCase {
private static final int MAX = 0;
private static final int MIN = 1;
private static final int SUM = 2;
private static final String[] numericAggregateFunctions = { "MAX", "AVG",
"MIN", "SUM" };
private static final String[] stringAggregateFunctions = { "MAX", "MIN" };
private static final String[] numericAttributes = { "ae.pintVal",
"ae.intVal", "ae.shortVal", "ae.pshortVal", "ae.pintVal",
"ae.intVal", "ae.plongVal", "ae.longVal", "ae.pfloatVal",
"ae.floatVal", "ae.pdblVal", "ae.dblVal" };
@Override
public void setUp() {
super.setUp(CLEAR_TABLES, AggEntity.class);
}
protected boolean nullResultExpected() {
return true;
}
public void testAggregateJPQL() {
EntityManager em = emf.createEntityManager();
// Verify all numeric types for all aggregate functions return null
// if there is no query result
verifyResult(em, numericAggregateFunctions, numericAttributes, true);
// Verify a string for all applicable aggregate functions return null
// if there is no query result
verifyResult(em, stringAggregateFunctions,
new String[] { "ae.stringVal" }, true, true);
// Add a row to the table and re-test
AggEntity ae = new AggEntity();
ae.init();
em.getTransaction().begin();
em.persist(ae);
em.getTransaction().commit();
// Verify all numeric types for all aggregate functions return a
// non-null value when there is a query result
verifyResult(em, numericAggregateFunctions, numericAttributes, false);
// Verify string types for all applicable aggregate functions return a
// non-null value when there is a query result
verifyResult(em, stringAggregateFunctions,
new String[] { "ae.stringVal" }, false);
em.close();
}
public void testAggregateCriteria() {
EntityManager em = emf.createEntityManager();
Metamodel mm = emf.getMetamodel();
mm.getEntities();
Query q = null;
// Verify all types of criteria query that return a Numeric type
for (int agg = MAX; agg <= SUM; agg++) {
CriteriaQuery<Short> cqs = buildNumericCriteriaQuery(em,
Short.class, AggEntity_.shortVal, agg);
q = em.createQuery(cqs);
verifyQueryResult(q, true);
cqs = buildNumericCriteriaQuery(em, Short.class,
AggEntity_.pshortVal, agg);
q = em.createQuery(cqs);
verifyQueryResult(q, true);
CriteriaQuery<Integer> cqi = buildNumericCriteriaQuery(em,
Integer.class, AggEntity_.intVal, agg);
q = em.createQuery(cqi);
verifyQueryResult(q, true);
cqi = buildNumericCriteriaQuery(em, Integer.class,
AggEntity_.pintVal, agg);
q = em.createQuery(cqi);
verifyQueryResult(q, true);
CriteriaQuery<Float> cqf = buildNumericCriteriaQuery(em,
Float.class, AggEntity_.floatVal, agg);
q = em.createQuery(cqf);
verifyQueryResult(q, true);
cqf = buildNumericCriteriaQuery(em, Float.class,
AggEntity_.pfloatVal, agg);
q = em.createQuery(cqi);
verifyQueryResult(q, true);
CriteriaQuery<Double> cqd = buildNumericCriteriaQuery(em,
Double.class, AggEntity_.dblVal, agg);
q = em.createQuery(cqd);
verifyQueryResult(q, true);
cqd = buildNumericCriteriaQuery(em, Double.class,
AggEntity_.pdblVal, agg);
q = em.createQuery(cqi);
verifyQueryResult(q, true);
}
// Verify AVG criteria query - it strictly returns type 'Double' so
// unlike other aggregates,
// it cannot be handled generically (as Numeric).
CriteriaQuery<Double> cqd = buildAvgCriteriaQuery(em, Double.class,
AggEntity_.dblVal);
q = em.createQuery(cqd);
verifyQueryResult(q, true);
cqd = buildAvgCriteriaQuery(em, Double.class, AggEntity_.pdblVal);
q = em.createQuery(cqd);
verifyQueryResult(q, true);
em.close();
}
private <T extends Number> CriteriaQuery<T> buildNumericCriteriaQuery(
EntityManager em, Class<T> type,
SingularAttribute<AggEntity, T> sa, int at) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(type);
Root<AggEntity> aer = cq.from(AggEntity.class);
Path<T> path = aer.get(sa);
Expression<T> exp = null;
switch (at) {
case MAX:
exp = cb.max(path);
break;
case MIN:
exp = cb.min(path);
break;
case SUM:
exp = cb.sum(path);
break;
}
cq.select(exp);
return cq;
}
private CriteriaQuery<Double> buildAvgCriteriaQuery(EntityManager em,
Class<Double> type, SingularAttribute<AggEntity, Double> sa) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Double> cq = cb.createQuery(type);
Root<AggEntity> aer = cq.from(AggEntity.class);
return cq.select(cb.avg(aer.get(sa)));
}
private void verifyResult(EntityManager em, String[] aggregates,
String[] attributes, boolean expectNull) {
verifyResult(em, aggregates, attributes, expectNull, false);
}
private void verifyResult(EntityManager em, String[] aggregates,
String[] attributes, boolean expectNull, boolean isString) {
for (String func : aggregates) {
for (String attr : attributes) {
// JPQL with aggregate and aggregate in subselect
String sql = "SELECT " + func + "(" + attr + ")"
+ " FROM AggEntity ae WHERE " + attr + " <= "
+ "(SELECT " + func + "("
+ attr.replaceFirst("^ae.", "ae2.")
+ ") FROM AggEntity ae2)";
Query q = em.createQuery(sql);
verifyQueryResult(q, expectNull, isString);
}
}
}
private void verifyQueryResult(Query q, boolean emptyRs) {
verifyQueryResult(q, emptyRs, false);
}
private void verifyQueryResult(Query q, boolean emptyRs, boolean isString) {
Object result = q.getSingleResult();
if (!emptyRs && !isString) {
assertNotNull(result);
} else if (isString || nullResultExpected()) {
assertNull(result);
} else {
assertNotNull(result);
}
List<?> resultList = q.getResultList();
assertEquals(1, resultList.size());
if (!emptyRs && !isString) {
assertNotNull(result);
} else if (isString || nullResultExpected()) {
assertNull(result);
} else {
assertNotNull(result);
}
}
}