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>
+    &lt;property name="openjpa.Compatibility" value="ReturnNullOnAggregateResult=false"/&gt;
+                    </programlisting>
+                </para>
+            </section>
         </section>
     </section>
 </appendix>