You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by jr...@apache.org on 2013/07/23 20:26:43 UTC
svn commit: r1506198 - in /openjpa/trunk:
openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/
openjpa-kernel/src/main/java/org/apache/openjpa/conf/
openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/
openjpa-persistence-jdbc...
Author: jrbauer
Date: Tue Jul 23 18:26:42 2013
New Revision: 1506198
URL: http://svn.apache.org/r1506198
Log:
OPENJPA-1794 aggregate function with no result set must return null
Added:
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullableAggregateUnaryOp.java
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity.java
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity_.java
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestAggregateFunctions.java
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestCompatAggregateFunctions.java
Modified:
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Avg.java
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Max.java
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Min.java
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sum.java
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/UnaryOp.java
openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/Compatibility.java
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestAggregateQueryWithNoResult.java
openjpa/trunk/openjpa-project/src/doc/manual/migration_considerations.xml
Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Avg.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Avg.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Avg.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Avg.java Tue Jul 23 18:26:42 2013
@@ -23,8 +23,9 @@ package org.apache.openjpa.jdbc.kernel.e
*
* @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;
}
}
-
Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Max.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Max.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Max.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Max.java Tue Jul 23 18:26:42 2013
@@ -23,8 +23,9 @@ package org.apache.openjpa.jdbc.kernel.e
*
* @author Abe White
*/
+@SuppressWarnings("serial")
class Max
- extends UnaryOp {
+ extends NullableAggregateUnaryOp { // OPENJPA-1794
/**
* Constructor. Provide the value to operate on.
Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Min.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Min.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Min.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Min.java Tue Jul 23 18:26:42 2013
@@ -23,8 +23,9 @@ package org.apache.openjpa.jdbc.kernel.e
*
* @author Abe White
*/
+@SuppressWarnings("serial")
class Min
- extends UnaryOp {
+ extends NullableAggregateUnaryOp { // OPENJPA-1794
/**
* Constructor. Provide the value to operate on.
Added: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullableAggregateUnaryOp.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullableAggregateUnaryOp.java?rev=1506198&view=auto
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullableAggregateUnaryOp.java (added)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NullableAggregateUnaryOp.java Tue Jul 23 18:26:42 2013
@@ -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;
+ }
+}
Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sum.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sum.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sum.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sum.java Tue Jul 23 18:26:42 2013
@@ -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.
Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/UnaryOp.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/UnaryOp.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/UnaryOp.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/UnaryOp.java Tue Jul 23 18:26:42 2013
@@ -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;
+ }
+}
Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/Compatibility.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/Compatibility.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/Compatibility.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/Compatibility.java Tue Jul 23 18:26:42 2013
@@ -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;
+ }
}
Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity.java?rev=1506198&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity.java Tue Jul 23 18:26:42 2013
@@ -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");
+ }
+}
Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity_.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity_.java?rev=1506198&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity_.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/AggEntity_.java Tue Jul 23 18:26:42 2013
@@ -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;
+}
Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestAggregateFunctions.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestAggregateFunctions.java?rev=1506198&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestAggregateFunctions.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestAggregateFunctions.java Tue Jul 23 18:26:42 2013
@@ -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);
+ }
+ }
+}
Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestCompatAggregateFunctions.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestCompatAggregateFunctions.java?rev=1506198&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestCompatAggregateFunctions.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jira1794/TestCompatAggregateFunctions.java Tue Jul 23 18:26:42 2013
@@ -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;
+ }
+}
Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestAggregateQueryWithNoResult.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestAggregateQueryWithNoResult.java?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestAggregateQueryWithNoResult.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestAggregateQueryWithNoResult.java Tue Jul 23 18:26:42 2013
@@ -38,7 +38,9 @@ import org.apache.openjpa.persistence.te
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());
Modified: openjpa/trunk/openjpa-project/src/doc/manual/migration_considerations.xml
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-project/src/doc/manual/migration_considerations.xml?rev=1506198&r1=1506197&r2=1506198&view=diff
==============================================================================
--- openjpa/trunk/openjpa-project/src/doc/manual/migration_considerations.xml (original)
+++ openjpa/trunk/openjpa-project/src/doc/manual/migration_considerations.xml Tue Jul 23 18:26:42 2013
@@ -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>