#192 improved sum() support

This commit is contained in:
Timo Westkämper 2012-07-05 23:38:07 +03:00
parent 8b492e2a2c
commit 67c477bf15
11 changed files with 231 additions and 75 deletions

View File

@ -0,0 +1,56 @@
/*
* Copyright 2012, Mysema Ltd
*
* Licensed 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 com.mysema.query.support;
import java.util.Collections;
import java.util.List;
import com.mysema.query.types.Expression;
import com.mysema.query.types.ExpressionBase;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.Visitor;
import com.mysema.util.MathUtils;
/**
* @author tiwe
*
* @param <T>
*/
public class NumberConversion<T> extends ExpressionBase<T> implements FactoryExpression<T> {
private static final long serialVersionUID = 7840412008633901748L;
private final List<Expression<?>> exprs;
public NumberConversion(Expression<T> expr) {
super(expr.getType());
exprs = Collections.<Expression<?>>singletonList(expr);
}
@Override
public <R, C> R accept(Visitor<R, C> v, C context) {
return v.visit(this, context);
}
@Override
public List<Expression<?>> getArgs() {
return exprs;
}
@Override
public T newInstance(Object... args) {
return (T)MathUtils.cast((Number)args[0], (Class)getType());
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2012, Mysema Ltd
*
* Licensed 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 com.mysema.query.support;
import java.util.List;
import com.mysema.query.types.Expression;
import com.mysema.query.types.ExpressionBase;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.Visitor;
import com.mysema.util.MathUtils;
/**
* @author tiwe
*
* @param <T>
*/
public class NumberConversions<T> extends ExpressionBase<T> implements FactoryExpression<T> {
private static final long serialVersionUID = -7834053123363933721L;
private final FactoryExpression<T> expr;
public NumberConversions(FactoryExpression<T> expr) {
super(expr.getType());
this.expr = expr;
}
@Override
public <R, C> R accept(Visitor<R, C> v, C context) {
return v.visit(this, context);
}
@Override
public List<Expression<?>> getArgs() {
return expr.getArgs();
}
@Override
public T newInstance(Object... args) {
for (int i = 0; i < args.length; i++) {
Class<?> type = expr.getArgs().get(i).getType();
if (args[i] instanceof Number && !args[i].getClass().equals(type)) {
args[i] = MathUtils.cast((Number)args[i], (Class)type);
}
}
return expr.newInstance(args);
}
}

View File

@ -19,8 +19,6 @@ import com.mysema.query.types.Expression;
import com.mysema.query.types.Ops;
import com.mysema.query.types.expr.ComparableExpression;
import com.mysema.query.types.expr.ComparableOperation;
import com.mysema.query.types.expr.NumberExpression;
import com.mysema.query.types.expr.NumberOperation;
import com.mysema.query.types.expr.StringExpression;
import com.mysema.query.types.expr.StringOperation;
@ -46,30 +44,6 @@ public final class JPQLGrammar {
public static <A extends Comparable<? super A>> ComparableExpression<A> min(CollectionExpression<?,A> left) {
return ComparableOperation.create((Class)left.getParameter(0), Ops.QuantOps.MIN_IN_COL, (Expression<?>)left);
}
/**
* SUM returns Long when applied to state-fields of integral types (other
* than BigInteger); Double when applied to state-fields of floating point
* types; BigInteger when applied to state-fields of type BigInteger; and
* BigDecimal when applied to state-fields of type BigDecimal.
*/
public static <D extends Number & Comparable<? super D>> NumberExpression<?> sum(Expression<D> left) {
Class<?> type = left.getType();
if (type.equals(Byte.class) || type.equals(Integer.class) || type.equals(Short.class)) {
type = Long.class;
} else if (type.equals(Float.class)) {
type = Double.class;
}
return NumberOperation.create((Class<D>) type, Ops.AggOps.SUM_AGG, left);
}
public static <D extends Number & Comparable<? super D>> NumberExpression<Long> sumAsLong(Expression<D> left) {
return sum(left).longValue();
}
public static <D extends Number & Comparable<? super D>> NumberExpression<Double> sumAsDouble(Expression<D> left) {
return sum(left).doubleValue();
}
public static StringExpression type(EntityPath<?> path) {
return StringOperation.create(JPQLTemplates.TYPE, path);

View File

@ -23,10 +23,15 @@ import com.mysema.query.JoinFlag;
import com.mysema.query.QueryMetadata;
import com.mysema.query.support.Context;
import com.mysema.query.support.ListAccessVisitor;
import com.mysema.query.support.NumberConversion;
import com.mysema.query.support.NumberConversions;
import com.mysema.query.support.QueryMixin;
import com.mysema.query.types.EntityPath;
import com.mysema.query.types.Expression;
import com.mysema.query.types.ExpressionUtils;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.Operation;
import com.mysema.query.types.Ops;
import com.mysema.query.types.Path;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.TemplateExpressionImpl;
@ -75,6 +80,32 @@ public class JPQLQueryMixin<T> extends QueryMixin<T> {
}
return getSelf();
}
public <RT> Expression<RT> convert(Expression<RT> expr){
if (isAggSumWithConversion(expr)) {
expr = new NumberConversion(expr);
} else if (expr instanceof FactoryExpression) {
FactoryExpression<?> factorye = (FactoryExpression<?>)expr;
for (Expression e : factorye.getArgs()) {
if (isAggSumWithConversion(e)) {
expr = new NumberConversions(factorye);
break;
}
}
}
return super.convert(expr);
}
private boolean isAggSumWithConversion(Expression<?> expr) {
if (expr instanceof Operation && ((Operation)expr).getOperator() == Ops.AggOps.SUM_AGG) {
Class type = ((Operation)expr).getType();
if (type.equals(Float.class) || type.equals(Integer.class) || type.equals(Short.class) || type.equals(Byte.class)) {
return true;
}
}
return false;
}
@Override
protected Predicate normalize(Predicate predicate, boolean where) {

View File

@ -37,6 +37,7 @@ import javax.persistence.metamodel.SingularAttribute;
import com.mysema.query.JoinExpression;
import com.mysema.query.JoinType;
import com.mysema.query.QueryMetadata;
import com.mysema.query.support.Expressions;
import com.mysema.query.support.SerializerBase;
import com.mysema.query.types.Constant;
import com.mysema.query.types.ConstantImpl;

View File

@ -13,7 +13,6 @@
*/
package com.mysema.query;
import static com.mysema.query.jpa.JPQLGrammar.sum;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -55,6 +54,7 @@ import com.mysema.query.jpa.domain.Company.Rating;
import com.mysema.query.jpa.domain.DomesticCat;
import com.mysema.query.jpa.domain.DoubleProjection;
import com.mysema.query.jpa.domain.Employee;
import com.mysema.query.jpa.domain.FloatProjection;
import com.mysema.query.jpa.domain.Foo;
import com.mysema.query.jpa.domain.JobFunction;
import com.mysema.query.jpa.domain.QAnimal;
@ -64,6 +64,7 @@ import com.mysema.query.jpa.domain.QCat;
import com.mysema.query.jpa.domain.QCompany;
import com.mysema.query.jpa.domain.QDoubleProjection;
import com.mysema.query.jpa.domain.QEmployee;
import com.mysema.query.jpa.domain.QFloatProjection;
import com.mysema.query.jpa.domain.QFoo;
import com.mysema.query.jpa.domain.QShow;
import com.mysema.query.jpa.domain.QUser;
@ -709,14 +710,14 @@ public abstract class AbstractStandardTest {
@Ignore
public void Sum() throws RecognitionException, TokenStreamException {
// NOT SUPPORTED
query().from(cat).list(sum(cat.kittens.size()));
query().from(cat).list(cat.kittens.size().sum());
}
@Test
@Ignore
public void Sum_2() throws RecognitionException, TokenStreamException {
// NOT SUPPORTED
query().from(cat).where(sum(cat.kittens.size()).gt(0)).list(cat);
query().from(cat).where(cat.kittens.size().sum().gt(0)).list(cat);
}
@Test
@ -731,6 +732,19 @@ public abstract class AbstractStandardTest {
assertEquals(val, projection.val, 0.001);
}
@Test
public void Sum_as_Float() {
float val = query().from(cat).uniqueResult(cat.floatProperty.sum());
assertTrue(val > 0);
}
@Test
public void Sum_as_Float_Projected() {
float val = query().from(cat).uniqueResult(cat.floatProperty.sum());
FloatProjection projection = query().from(cat).uniqueResult(new QFloatProjection(cat.floatProperty.sum()));
assertEquals(val, projection.val, 0.001);
}
@Test
public void Substring() {
for (String str : query().from(cat).list(cat.name.substring(1,2))) {

View File

@ -13,19 +13,14 @@
*/
package com.mysema.query.jpa;
import static com.mysema.query.jpa.JPQLGrammar.sum;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.junit.Test;
import com.mysema.query.jpa.domain.QAccount;
import com.mysema.query.jpa.domain.QInheritedProperties;
import com.mysema.query.types.expr.NumberExpression;
import com.mysema.query.types.path.NumberPath;
public class FeaturesTest extends AbstractQueryTest {
@ -139,35 +134,35 @@ public class FeaturesTest extends AbstractQueryTest {
// toString("distinct cat.bodyWeight", distinct(cat.bodyWeight));
}
/**
* specs :
* http://opensource.atlassian.com/projects/hibernate/browse/HHH-1538
*/
@SuppressWarnings("unchecked")
@Test
public void Bug326650() {
assertEquals(Long.class, sum(var(Byte.class)).getType());
assertEquals(Long.class, sum(var(Short.class)).getType());
assertEquals(Long.class, sum(var(Integer.class)).getType());
assertEquals(Long.class, sum(var(Long.class)).getType());
assertEquals(Double.class, sum(var(Float.class)).getType());
assertEquals(Double.class, sum(var(Double.class)).getType());
assertEquals(BigInteger.class, sum(var(BigInteger.class)).getType());
assertEquals(BigDecimal.class, sum(var(BigDecimal.class)).getType());
// sum to var
NumberExpression<Long> sum = (NumberExpression) sum(var(Integer.class)); // via Java level cast
sum = sum(var(Integer.class)).longValue();
assertNotNull(sum);
// sum comparison
sum(var(Integer.class)).gt(0);
sum(var(Integer.class)).intValue().gt(0);
}
// /**
// * specs :
// * http://opensource.atlassian.com/projects/hibernate/browse/HHH-1538
// */
// @SuppressWarnings("unchecked")
// @Test
// public void Bug326650() {
// assertEquals(Long.class, sum(var(Byte.class)).getType());
// assertEquals(Long.class, sum(var(Short.class)).getType());
// assertEquals(Long.class, sum(var(Integer.class)).getType());
// assertEquals(Long.class, sum(var(Long.class)).getType());
//
// assertEquals(Double.class, sum(var(Float.class)).getType());
// assertEquals(Double.class, sum(var(Double.class)).getType());
//
// assertEquals(BigInteger.class, sum(var(BigInteger.class)).getType());
// assertEquals(BigDecimal.class, sum(var(BigDecimal.class)).getType());
//
// // sum to var
// NumberExpression<Long> sum = (NumberExpression) sum(var(Integer.class)); // via Java level cast
// sum = sum(var(Integer.class)).longValue();
// assertNotNull(sum);
//
// // sum comparison
//
// sum(var(Integer.class)).gt(0);
// sum(var(Integer.class)).intValue().gt(0);
//
// }
private <D extends Number & Comparable<?>> NumberPath<D> var(Class<D> cl){
return new NumberPath<D>(cl, "var");

View File

@ -15,7 +15,6 @@ package com.mysema.query.jpa;
import static com.mysema.query.alias.Alias.$;
import static com.mysema.query.alias.Alias.alias;
import static com.mysema.query.jpa.JPQLGrammar.sum;
import static org.junit.Assert.assertEquals;
import org.junit.Ignore;
@ -70,7 +69,7 @@ public class ParsingTest extends AbstractQueryTest{
public void DocoExamples910() throws Exception {
query().from(cat)
.groupBy(cat.color)
.select(cat.color, sum(cat.weight), cat.count()).parse();
.select(cat.color, cat.weight.sum(), cat.count()).parse();
}
@Test
@ -78,7 +77,7 @@ public class ParsingTest extends AbstractQueryTest{
query().from(cat)
.groupBy(cat.color)
.having(cat.color.in(Color.TABBY, Color.BLACK))
.select(cat.color, sum(cat.weight), cat.count()).parse();
.select(cat.color, cat.weight.sum(), cat.count()).parse();
}
@Test
@ -87,7 +86,7 @@ public class ParsingTest extends AbstractQueryTest{
query().from(cat).join(cat.kittens, kitten)
.groupBy(cat)
.having(kitten.weight.avg().gt(100.0))
.orderBy(kitten.count().asc(), sum(kitten.weight).desc())
.orderBy(kitten.count().asc(), kitten.weight.sum().desc())
.select(cat)
.parse();
}
@ -139,9 +138,9 @@ public class ParsingTest extends AbstractQueryTest{
sub().from(catalog).where(
catalog.effectiveDate.lt(DateExpression.currentDate()))
.list(catalog.effectiveDate))))
.groupBy(ord).having(sum(price.amount).gt(0l))
.orderBy(sum(price.amount).desc())
.select(ord.id, sum(price.amount), item.count());
.groupBy(ord).having(price.amount.sum().gt(0l))
.orderBy(price.amount.sum().desc())
.select(ord.id, price.amount.sum(), item.count());
Customer c1 = new Customer();
Catalog c2 = new Catalog();
@ -151,9 +150,9 @@ public class ParsingTest extends AbstractQueryTest{
.from(catalog).join(catalog.prices, price).where(
ord.paid.not().and(ord.customer.eq(c1)).and(
price.product.eq(product)).and(catalog.eq(c2)))
.groupBy(ord).having(sum(price.amount).gt(0l))
.orderBy(sum(price.amount).desc())
.select(ord.id, sum(price.amount), item.count());
.groupBy(ord).having(price.amount.sum().gt(0l))
.orderBy(price.amount.sum().desc())
.select(ord.id, price.amount.sum(), item.count());
}
@ -550,14 +549,14 @@ public class ParsingTest extends AbstractQueryTest{
@Ignore
public void Sum() throws RecognitionException, TokenStreamException {
// NOT SUPPORTED
query().from(cat).select(sum(cat.kittens.size())).parse();
query().from(cat).select(cat.kittens.size().sum()).parse();
}
@Test
@Ignore
public void Sum_2() throws RecognitionException, TokenStreamException {
// NOT SUPPORTED
query().from(cat).where(sum(cat.kittens.size()).gt(0)).select(cat).parse();
query().from(cat).where(cat.kittens.size().sum().gt(0)).select(cat).parse();
}
@Test

View File

@ -41,6 +41,8 @@ public class Animal {
// needed for JPA tests
@Type(type="com.mysema.query.ExtDoubleType")
private double bodyWeight;
private float floatProperty;
private Color color;
@ -140,4 +142,12 @@ public class Animal {
this.weight = weight;
}
public float getFloatProperty() {
return floatProperty;
}
public void setFloatProperty(float floatProperty) {
this.floatProperty = floatProperty;
}
}

View File

@ -67,6 +67,7 @@ public class Cat extends Animal {
public Cat(String name, int id, double bodyWeight){
this(name, id);
setBodyWeight(bodyWeight);
setFloatProperty((float)bodyWeight);
}
public int getBreed() {

View File

@ -0,0 +1,14 @@
package com.mysema.query.jpa.domain;
import com.mysema.query.annotations.QueryProjection;
public class FloatProjection {
public float val;
@QueryProjection
public FloatProjection(float val) {
this.val = val;
}
}