mirror of
https://github.com/querydsl/querydsl.git
synced 2026-07-03 21:07:49 +08:00
Another alternative for group by ResultTransformer
This commit is contained in:
parent
2a138217b4
commit
e281332656
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Mysema Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
package com.mysema.query.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.mysema.commons.lang.CloseableIterator;
|
||||
import com.mysema.query.Projectable;
|
||||
import com.mysema.query.ResultTransformer;
|
||||
import com.mysema.query.Tuple;
|
||||
import com.mysema.query.types.Expression;
|
||||
import com.mysema.query.types.Visitor;
|
||||
|
||||
/**
|
||||
* Groups results by the first expression.
|
||||
* <ol>
|
||||
* <li>Order of groups by position of the first row of a group
|
||||
* <li>Rows belonging to a group may appear in any order
|
||||
* <li>Group of null is handled correctly
|
||||
* </ol>
|
||||
*
|
||||
* @author sasa
|
||||
*/
|
||||
//@SuppressWarnings("unchecked")
|
||||
public class GroupBy2 implements ResultTransformer<Collection<Tuple>> {
|
||||
|
||||
private final Expression<?>[] expressions;
|
||||
|
||||
private static interface GroupFactory<T, R> {
|
||||
|
||||
public void add(Object o);
|
||||
|
||||
public R get();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This expression only applies to GroupBy.transform
|
||||
*
|
||||
* @param <T>
|
||||
* @param <R>
|
||||
*/
|
||||
public static abstract class GroupAsExpression<T, R> implements Expression<R> {
|
||||
|
||||
private static final long serialVersionUID = -8164758792405567077L;
|
||||
|
||||
private final Expression<T> expr;
|
||||
|
||||
public GroupAsExpression(Expression<T> expr) {
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, C> S accept(Visitor<S, C> v, C context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends R> getType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return expr.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
} else if (o instanceof GroupAsExpression) {
|
||||
GroupAsExpression<?, ?> other = (GroupAsExpression<?, ?>) o;
|
||||
return this.expr.equals(other.expr);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract GroupFactory<T, R> createGroupFactory();
|
||||
|
||||
}
|
||||
|
||||
public static <T> GroupAsExpression<T, Set<T>> set(Expression<T> expr) {
|
||||
return new GroupAsExpression<T, Set<T>>(expr) {
|
||||
|
||||
private static final long serialVersionUID = -2507144565843468159L;
|
||||
|
||||
@Override
|
||||
public GroupFactory<T, Set<T>> createGroupFactory() {
|
||||
return new GroupFactory<T, Set<T>>() {
|
||||
|
||||
private final Set<T> set = new HashSet<T>();
|
||||
|
||||
@Override
|
||||
public void add(Object o) {
|
||||
set.add((T) o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<T> get() {
|
||||
return set;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> GroupAsExpression<T, List<T>> list(Expression<T> expr) {
|
||||
return new GroupAsExpression<T, List<T>>(expr) {
|
||||
|
||||
private static final long serialVersionUID = -6941324182786049824L;
|
||||
|
||||
@Override
|
||||
public GroupFactory<T, List<T>> createGroupFactory() {
|
||||
return new GroupFactory<T, List<T>>() {
|
||||
|
||||
private final List<T> list = new ArrayList<T>();
|
||||
|
||||
@Override
|
||||
public void add(Object o) {
|
||||
list.add((T) o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> get() {
|
||||
return list;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private static class ValueGroupFactory<T> implements GroupFactory<T, T> {
|
||||
private T val;
|
||||
|
||||
private boolean first = true;
|
||||
|
||||
@Override
|
||||
public void add(Object o) {
|
||||
if (first) {
|
||||
val = (T) o;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GroupBy2(Expression<?> groupBy, Expression<?>... args) {
|
||||
expressions = new Expression<?>[args.length + 1];
|
||||
expressions[0] = groupBy;
|
||||
System.arraycopy(args, 0, expressions, 1, args.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Tuple> transform(Projectable projectable) {
|
||||
final LinkedHashMap<Object, Tuple> groups = new LinkedHashMap<Object, Tuple>();
|
||||
|
||||
CloseableIterator<Object[]> iter = projectable.iterate(unwrap(expressions));
|
||||
try {
|
||||
while (iter.hasNext()) {
|
||||
Object[] row = iter.next();
|
||||
Object groupBy = row[0];
|
||||
// groups.values() should return Collection<GTuple> instead of Collection<? extends GTuple>
|
||||
GroupTuple group = (GroupTuple) groups.get(groupBy);
|
||||
if (group == null) {
|
||||
group = new GroupTuple();
|
||||
groups.put(groupBy, group);
|
||||
}
|
||||
group.add(row);
|
||||
}
|
||||
} finally {
|
||||
iter.close();
|
||||
}
|
||||
return groups.values();
|
||||
}
|
||||
|
||||
private static Expression<?>[] unwrap(Expression<?>... expressions) {
|
||||
Expression<?>[] unwrapped = new Expression<?>[expressions.length];
|
||||
for (int i=0; i < expressions.length; i++) {
|
||||
if (expressions[i] instanceof GroupAsExpression) {
|
||||
unwrapped[i] = ((GroupAsExpression<?, ?>) expressions[i]).expr;
|
||||
} else {
|
||||
unwrapped[i] = expressions[i];
|
||||
}
|
||||
}
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
private int indexOf(Expression<?> expr) {
|
||||
for (int i=0; i < expressions.length; i++) {
|
||||
if (expressions[i].equals(expr)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private class GroupTuple implements Tuple {
|
||||
|
||||
private final GroupFactory<?, ?>[] groupFactories;
|
||||
|
||||
public GroupTuple() {
|
||||
groupFactories = new GroupFactory<?, ?>[expressions.length];
|
||||
for (int i=0; i < expressions.length; i++) {
|
||||
if (expressions[i] instanceof GroupAsExpression<?, ?>) {
|
||||
groupFactories[i] = ((GroupAsExpression<?, ?>) expressions[i]).createGroupFactory();
|
||||
} else {
|
||||
groupFactories[i] = new ValueGroupFactory<Object>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(int index, Class<T> type) {
|
||||
return (T) groupFactories[index].get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(Expression<T> expr) {
|
||||
int index = indexOf(expr);
|
||||
return (T) groupFactories[index].get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
Object[] row = new Object[groupFactories.length];
|
||||
for (int i=0; i < groupFactories.length; i++) {
|
||||
row[i] = groupFactories[i].get();
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
public void add(Object[] row) {
|
||||
for (int i=0; i < groupFactories.length; i++) {
|
||||
groupFactories[i].add(row[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
package com.mysema.query.support;
|
||||
|
||||
|
||||
import static com.mysema.query.support.GroupBy2.list;
|
||||
import static com.mysema.query.support.GroupBy2.set;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mysema.commons.lang.CloseableIterator;
|
||||
import com.mysema.commons.lang.EmptyCloseableIterator;
|
||||
import com.mysema.commons.lang.IteratorAdapter;
|
||||
import com.mysema.query.Projectable;
|
||||
import com.mysema.query.Tuple;
|
||||
import com.mysema.query.types.Expression;
|
||||
import com.mysema.query.types.expr.NumberExpression;
|
||||
import com.mysema.query.types.expr.StringExpression;
|
||||
import com.mysema.query.types.path.NumberPath;
|
||||
import com.mysema.query.types.path.StringPath;
|
||||
|
||||
public class GroupBy2Test {
|
||||
|
||||
private final NumberExpression<Integer> postId = new NumberPath<Integer>(Integer.class, "postId");
|
||||
|
||||
private final StringExpression postName = new StringPath("postName");
|
||||
|
||||
private final NumberExpression<Integer> commentId = new NumberPath<Integer>(Integer.class, "commentId");
|
||||
|
||||
static class PostWithComments {
|
||||
public Integer id;
|
||||
public String name;
|
||||
public Set<Integer> comments;
|
||||
public PostWithComments(Integer id, String name, Set<Integer> comments) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.comments = comments;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Expression_Order_And_Type() {
|
||||
new GroupBy2(postId, postName, set(commentId)).transform(new AbstractProjectable(){
|
||||
public CloseableIterator<Object[]> iterate(Expression<?>[] args) {
|
||||
assertEquals(postId, args[0]);
|
||||
assertEquals(postName, args[1]);
|
||||
assertEquals(commentId, args[2]);
|
||||
return new EmptyCloseableIterator<Object[]>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <ol>
|
||||
* <li>Order of groups by first row of a group
|
||||
* <li>Rows belonging to a group may appear in any order
|
||||
* <li>Group of null is handled correctly
|
||||
* </ol>
|
||||
*/
|
||||
@Test
|
||||
public void Multiple_Groups() {
|
||||
Collection<Tuple> results = new GroupBy2(postId, postName, list(commentId)).transform(
|
||||
projectable(
|
||||
row(1, "post 1", 1),
|
||||
row(2, "post 2", 4),
|
||||
row(1, "post 1", 2),
|
||||
row(2, "post 2", 5),
|
||||
row(3, "post 3", 6),
|
||||
row(null, "null post", 7),
|
||||
row(null, "null post", 8),
|
||||
row(1, "post 1", 3)
|
||||
)
|
||||
);
|
||||
assertEquals(4, results.size());
|
||||
Iterator<Tuple> iter = results.iterator();
|
||||
|
||||
Tuple g = iter.next();
|
||||
assertEquals(toInt(1), g.get(postId));
|
||||
assertEquals("post 1", g.get(postName));
|
||||
List<Integer> comments = g.get(list(commentId));
|
||||
assertEquals(toInt(1), comments.get(0));
|
||||
assertEquals(toInt(2), comments.get(1));
|
||||
assertEquals(toInt(3), comments.get(2));
|
||||
|
||||
g = iter.next();
|
||||
assertEquals(toInt(2), g.get(postId));
|
||||
assertEquals("post 2", g.get(postName));
|
||||
comments = g.get(list(commentId));
|
||||
assertEquals(toInt(4), comments.get(0));
|
||||
assertEquals(toInt(5), comments.get(1));
|
||||
|
||||
g = iter.next();
|
||||
assertEquals(toInt(3), g.get(postId));
|
||||
assertEquals("post 3", g.get(postName));
|
||||
comments = g.get(list(commentId));
|
||||
assertEquals(toInt(6), comments.get(0));
|
||||
|
||||
// Group by null value
|
||||
g = iter.next();
|
||||
assertEquals(null, g.get(postId));
|
||||
assertEquals("null post", g.get(postName));
|
||||
comments = g.get(list(commentId));
|
||||
assertEquals(toInt(7), comments.get(0));
|
||||
assertEquals(toInt(8), comments.get(1));
|
||||
}
|
||||
|
||||
private static <T> Set<T> toSet(T... o) {
|
||||
return new HashSet<T>(Arrays.asList(o));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Group_As_Set() {
|
||||
Collection<Tuple> results = new GroupBy2(postId, postName, set(commentId)).transform(projectable(
|
||||
row(1, "post 1", 1),
|
||||
row(null, "null post", 2)
|
||||
));
|
||||
assertEquals(2, results.size());
|
||||
Iterator<Tuple> iter = results.iterator();
|
||||
|
||||
Tuple group = iter.next();
|
||||
assertEquals(toInt(1), group.get(postId));
|
||||
assertEquals("post 1", group.get(postName));
|
||||
assertEquals(toSet(1), group.get(set(commentId)));
|
||||
|
||||
group = iter.next();
|
||||
assertEquals(null, group.get(postId));
|
||||
}
|
||||
|
||||
private Projectable projectable(final Object[]... rows) {
|
||||
return new AbstractProjectable(){
|
||||
public CloseableIterator<Object[]> iterate(Expression<?>[] args) {
|
||||
return iterator(rows);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Integer toInt(int i) {
|
||||
return Integer.valueOf(i);
|
||||
}
|
||||
|
||||
private static Object[] row(Object... row) {
|
||||
return row;
|
||||
}
|
||||
|
||||
private static CloseableIterator<Object[]> iterator(Object[]... rows) {
|
||||
return new IteratorAdapter<Object[]>(Arrays.asList(rows).iterator());
|
||||
}
|
||||
}
|
||||
@ -143,7 +143,7 @@ public class GroupByTest {
|
||||
|
||||
@Test
|
||||
public void Nested_Transformers() {
|
||||
// NOTE: This kind of bean projecting transformer can be generalized...
|
||||
// Maybe this kind of bean projecting transformer can be generalized...
|
||||
|
||||
// A transformer that uses results of GroupBy transformer to instantiate beans with collections
|
||||
List<PostWithComments> posts = new ResultTransformer<List<PostWithComments>>() {
|
||||
@ -192,8 +192,7 @@ public class GroupByTest {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> CloseableIterator<T> iterator(Object[]... rows) {
|
||||
return new IteratorAdapter(Arrays.asList(rows).iterator());
|
||||
private static CloseableIterator<Object[]> iterator(Object[]... rows) {
|
||||
return new IteratorAdapter<Object[]>(Arrays.asList(rows).iterator());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user