mirror of https://github.com/apache/openjpa.git
OPENJPA-1794 aggregate function with no result set must return null
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1506198 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5b3fea7cd4
commit
ca76712e38
|
@ -23,8 +23,9 @@ package org.apache.openjpa.jdbc.kernel.exps;
|
|||
*
|
||||
* @author Abe White
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class Avg
|
||||
extends UnaryOp {
|
||||
extends NullableAggregateUnaryOp { // OPENJPA-1794
|
||||
|
||||
/**
|
||||
* Constructor. Provide the value to operate on.
|
||||
|
@ -41,4 +42,3 @@ class Avg
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,9 @@ package org.apache.openjpa.jdbc.kernel.exps;
|
|||
*
|
||||
* @author Abe White
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class Max
|
||||
extends UnaryOp {
|
||||
extends NullableAggregateUnaryOp { // OPENJPA-1794
|
||||
|
||||
/**
|
||||
* Constructor. Provide the value to operate on.
|
||||
|
|
|
@ -23,8 +23,9 @@ package org.apache.openjpa.jdbc.kernel.exps;
|
|||
*
|
||||
* @author Abe White
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class Min
|
||||
extends UnaryOp {
|
||||
extends NullableAggregateUnaryOp { // OPENJPA-1794
|
||||
|
||||
/**
|
||||
* Constructor. Provide the value to operate on.
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.jdbc.kernel.exps;
|
||||
|
||||
/**
|
||||
* OPENJPA-1794
|
||||
* An aggregate unary operation that can indicate whether a null value from the data store
|
||||
* should be returned as null.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class NullableAggregateUnaryOp extends UnaryOp {
|
||||
|
||||
public NullableAggregateUnaryOp(Val val) {
|
||||
super(val);
|
||||
}
|
||||
|
||||
public NullableAggregateUnaryOp(Val val, boolean noParen) {
|
||||
super(val, noParen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nullableValue(ExpContext ctx, ExpState state) {
|
||||
// If this is a simple operator (no joins involved), check compatibility to determine
|
||||
// whether 'null' should be returned for the aggregate operation
|
||||
if (ctx != null && ctx.store != null && (state.joins == null || state.joins.isEmpty())) {
|
||||
return ctx.store.getConfiguration().getCompatibilityInstance().getReturnNullOnEmptyAggregateResult();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -25,8 +25,9 @@ import org.apache.openjpa.kernel.Filters;
|
|||
*
|
||||
* @author Abe White
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class Sum
|
||||
extends UnaryOp {
|
||||
extends NullableAggregateUnaryOp { // OPENJPA-1794
|
||||
|
||||
/**
|
||||
* Constructor. Provide the value to operate on.
|
||||
|
|
|
@ -119,8 +119,13 @@ abstract class UnaryOp
|
|||
throws SQLException {
|
||||
Object value = res.getObject(this, JavaSQLTypes.JDBC_DEFAULT, null);
|
||||
Class<?> type = getType();
|
||||
if (value == null && (type.isPrimitive() || Number.class.isAssignableFrom(type))) {
|
||||
value = Filters.getDefaultForNull(Filters.wrap(type));
|
||||
if (value == null) {
|
||||
if (nullableValue(ctx, state)) { // OPENJPA-1794
|
||||
return null;
|
||||
}
|
||||
else if (type.isPrimitive() || Number.class.isAssignableFrom(type)) {
|
||||
value = Filters.getDefaultForNull(Filters.wrap(type));
|
||||
}
|
||||
}
|
||||
return Filters.convert(value, type);
|
||||
}
|
||||
|
@ -171,5 +176,10 @@ abstract class UnaryOp
|
|||
_val.acceptVisit(visitor);
|
||||
visitor.exit(this);
|
||||
}
|
||||
}
|
||||
|
||||
// OPENJPA-1794
|
||||
protected boolean nullableValue(ExpContext ctx, ExpState state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -76,7 +76,8 @@ public class Compatibility {
|
|||
private boolean _resetFlushFlagForCascadePersist = true;//OPENJPA-2051
|
||||
private boolean _singletonLifecycleEventManager = false;
|
||||
private boolean _filterPCRegistryClasses = false; // OPENJPA-2288
|
||||
|
||||
private boolean _returnNullOnEmptyAggregateResult = true; // OPENJPA-1794
|
||||
|
||||
/**
|
||||
* Whether to require exact identity value types when creating object
|
||||
* ids from a class and value. Defaults to false.
|
||||
|
@ -728,4 +729,38 @@ public class Compatibility {
|
|||
public void setFilterPCRegistryClasses(boolean bool) {
|
||||
_filterPCRegistryClasses = bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* This property is used to specify whether the aggregate query functions
|
||||
* SUM, AVG, MAX, and MIN return null if there is no query result. This will occur
|
||||
* if no rows are returned for the specified query predicate. The default is
|
||||
* false, meaning that 0 will be returned for functions operating on numeric
|
||||
* data.
|
||||
*
|
||||
* In compliance with the JPA specification, the default value is true.
|
||||
*
|
||||
* @return true if the result of an aggregate with an empty query result returns null.
|
||||
* @since
|
||||
*
|
||||
*/
|
||||
public boolean getReturnNullOnEmptyAggregateResult() {
|
||||
return _returnNullOnEmptyAggregateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* This property is used to specify whether the aggregate query functions
|
||||
* SUM, AVG, MAX, and MIN return null if there is no query result. This will occur
|
||||
* if no rows are returned for the specified query predicate. The default is
|
||||
* false, meaning that 0 will be returned for functions operating on numeric
|
||||
* data.
|
||||
*
|
||||
* In compliance with the JPA specification, the default value is true.
|
||||
*
|
||||
* @since
|
||||
* @param returnNullOnAggregate whether OpenJPA will return null for aggregate
|
||||
* expressions when the query result is empty.
|
||||
*/
|
||||
public void setReturnNullOnAggregateResult(boolean returnNullOnEmptyAggregateResult) {
|
||||
_returnNullOnEmptyAggregateResult = returnNullOnEmptyAggregateResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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 javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "j1794_ae")
|
||||
public class AggEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private int id;
|
||||
|
||||
private short pshortVal;
|
||||
private Short shortVal;
|
||||
|
||||
private int pintVal;
|
||||
private Integer intVal;
|
||||
|
||||
private long plongVal;
|
||||
private Long longVal;
|
||||
|
||||
private float pfloatVal;
|
||||
private Float floatVal;
|
||||
|
||||
private double pdblVal;
|
||||
private Double dblVal;
|
||||
|
||||
private String stringVal;
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setPshortVal(short pshortVal) {
|
||||
this.pshortVal = pshortVal;
|
||||
}
|
||||
|
||||
public short getPshortVal() {
|
||||
return pshortVal;
|
||||
}
|
||||
|
||||
public void setShortVal(Short pShortVal) {
|
||||
this.shortVal = pShortVal;
|
||||
}
|
||||
|
||||
public Short getShortVal() {
|
||||
return shortVal;
|
||||
}
|
||||
|
||||
public void setPintVal(int pintVal) {
|
||||
this.pintVal = pintVal;
|
||||
}
|
||||
|
||||
public int getPintVal() {
|
||||
return pintVal;
|
||||
}
|
||||
|
||||
public void setIntVal(Integer intVal) {
|
||||
this.intVal = intVal;
|
||||
}
|
||||
|
||||
public Integer getIntVal() {
|
||||
return intVal;
|
||||
}
|
||||
|
||||
public void setPlongVal(long plongVal) {
|
||||
this.plongVal = plongVal;
|
||||
}
|
||||
|
||||
public long getPlongVal() {
|
||||
return plongVal;
|
||||
}
|
||||
|
||||
public void setLongVal(Long longVal) {
|
||||
this.longVal = longVal;
|
||||
}
|
||||
|
||||
public Long getLongVal() {
|
||||
return longVal;
|
||||
}
|
||||
|
||||
public void setPfloatVal(float pfloatVal) {
|
||||
this.pfloatVal = pfloatVal;
|
||||
}
|
||||
|
||||
public float getPfloatVal() {
|
||||
return pfloatVal;
|
||||
}
|
||||
|
||||
public void setFloatVal(Float floatVal) {
|
||||
this.floatVal = floatVal;
|
||||
}
|
||||
|
||||
public Float getFloatVal() {
|
||||
return floatVal;
|
||||
}
|
||||
|
||||
public void setPdblVal(double pdblVal) {
|
||||
this.pdblVal = pdblVal;
|
||||
}
|
||||
|
||||
public double getPdblVal() {
|
||||
return pdblVal;
|
||||
}
|
||||
|
||||
public void setDblVal(Double dblVal) {
|
||||
this.dblVal = dblVal;
|
||||
}
|
||||
|
||||
public Double getDblVal() {
|
||||
return dblVal;
|
||||
}
|
||||
|
||||
public void setStringVal(String stringVal) {
|
||||
this.stringVal = stringVal;
|
||||
}
|
||||
|
||||
public String getStringVal() {
|
||||
return stringVal;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
setPshortVal((short) 1);
|
||||
setShortVal(Short.valueOf((short) 1));
|
||||
setIntVal(1);
|
||||
setPintVal(1);
|
||||
setLongVal(1L);
|
||||
setPlongVal(1L);
|
||||
setDblVal(1d);
|
||||
setPdblVal(1d);
|
||||
setFloatVal(1f);
|
||||
setPfloatVal(1f);
|
||||
setStringVal("1");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generated by OpenJPA MetaModel Generator Tool.
|
||||
**/
|
||||
package org.apache.openjpa.jira1794;
|
||||
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
@javax.persistence.metamodel.StaticMetamodel
|
||||
(value=org.apache.openjpa.jira1794.AggEntity.class)
|
||||
public class AggEntity_ {
|
||||
public static volatile SingularAttribute<AggEntity,Short> pshortVal;
|
||||
public static volatile SingularAttribute<AggEntity,Short> shortVal;
|
||||
public static volatile SingularAttribute<AggEntity,Integer> pintVal;
|
||||
public static volatile SingularAttribute<AggEntity,Integer> intVal;
|
||||
public static volatile SingularAttribute<AggEntity,Long> plongVal;
|
||||
public static volatile SingularAttribute<AggEntity,Long> longVal;
|
||||
public static volatile SingularAttribute<AggEntity,Float> pfloatVal;
|
||||
public static volatile SingularAttribute<AggEntity,Float> floatVal;
|
||||
public static volatile SingularAttribute<AggEntity,Double> pdblVal;
|
||||
public static volatile SingularAttribute<AggEntity,Double> dblVal;
|
||||
public static volatile SingularAttribute<AggEntity,String> stringVal;
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* OPENJPA-1794
|
||||
* Verifies the return value of aggregate functions when a query result
|
||||
* set is empty. In this variation, the compatibility flag
|
||||
* is set so 0 is expected.
|
||||
*/
|
||||
public class TestCompatAggregateFunctions extends TestAggregateFunctions {
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp(CLEAR_TABLES,
|
||||
"openjpa.Compatibility", "ReturnNullOnAggregateResult=false",
|
||||
AggEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
// In compatibility mode a null result is not expected.
|
||||
protected boolean nullResultExpected() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -38,7 +38,9 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
|||
public class TestAggregateQueryWithNoResult extends SingleEMFTestCase {
|
||||
EntityManager em;
|
||||
public void setUp() {
|
||||
super.setUp(CLEAR_TABLES, Game.class, IndoorGame.class, Scrabble.class,
|
||||
super.setUp(CLEAR_TABLES,
|
||||
"openjpa.Compatibility", "ReturnNullOnAggregateResult=false", //OPENJPA-1794
|
||||
Game.class, IndoorGame.class, Scrabble.class,
|
||||
Chess.class);
|
||||
em = emf.createEntityManager();
|
||||
assertTrue(em.createQuery("select p from Scrabble p").getResultList().isEmpty());
|
||||
|
|
|
@ -557,6 +557,25 @@
|
|||
RequiresSearchStringEscapeForLike property to be true if the old behavior is desired.
|
||||
</para>
|
||||
</section>
|
||||
<section id="ReturnNullOnEmptyAggregateResult">
|
||||
<title>
|
||||
Return value of aggregate functions in SELECT clause
|
||||
</title>
|
||||
<!-- See OPENJPA-1794 for details. -->
|
||||
<para>
|
||||
The JPA specification states "If SUM, AVG, MAX, or MIN is used, and there are no values to which the aggregate function can be
|
||||
applied, the result of the aggregate function is NULL." Prior to this update, OpenJPA incorrectly returned 0 for SUM, AVG, MIN,
|
||||
and MAX when a state field being aggregated is numeric. This behavior affects both JPQL and Criteria queries. With this update,
|
||||
OpenJPA will return a null result value for these aggregate functions when a query returns no result.
|
||||
</para>
|
||||
<para>
|
||||
To re-enable the prior behavior, you need to set the following persistence property in your persistence.xml or when
|
||||
creating an EntityManagerFactory.
|
||||
<programlisting>
|
||||
<property name="openjpa.Compatibility" value="ReturnNullOnAggregateResult=false"/>
|
||||
</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</appendix>
|
||||
|
|
Loading…
Reference in New Issue