mirror of
https://github.com/querydsl/querydsl.git
synced 2026-07-03 21:07:49 +08:00
ResultTransformer interface and implementation for grouping of projected rows
This commit is contained in:
parent
d33e0729bb
commit
f35479759d
@ -0,0 +1,8 @@
|
||||
package com.mysema.query;
|
||||
|
||||
|
||||
public interface ResultTransformer<T> {
|
||||
|
||||
public T transform(Projectable projectable);
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Mysema Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
package com.mysema.query.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.mysema.query.types.Expression;
|
||||
|
||||
/**
|
||||
* An interface for grouped results. Group identifier is always the first element.
|
||||
*
|
||||
* @author sasa
|
||||
*/
|
||||
public interface Group {
|
||||
|
||||
/**
|
||||
* Get an element of first row of this group by index
|
||||
*
|
||||
* @param <T>
|
||||
* @param index
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
<T> T get(int index, Class<T> type);
|
||||
|
||||
/**
|
||||
* Get an element of first row of this group by expr
|
||||
*
|
||||
* @param <T>
|
||||
* @param expr
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
<T> T get(Expression<T> expr);
|
||||
|
||||
/**
|
||||
* Get a list of elements (all rows) of this group by expression
|
||||
*
|
||||
* @param <T>
|
||||
* @param index
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
<T> List<T> getList(int index, Class<T> type);
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of elements (all rows) of this group by expression
|
||||
*
|
||||
* @param <T>
|
||||
* @param expr
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
<T> List<T> getList(Expression<T> expr);
|
||||
|
||||
int size();
|
||||
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Mysema Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
package com.mysema.query.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.mysema.commons.lang.CloseableIterator;
|
||||
import com.mysema.query.Projectable;
|
||||
import com.mysema.query.ResultTransformer;
|
||||
import com.mysema.query.types.Expression;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public class GroupBy implements ResultTransformer<Collection<Group>> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Expression<?>[] expressions;
|
||||
|
||||
public GroupBy(Expression<?> groupBy, Expression<?>... args) {
|
||||
expressions = new Expression<?>[args.length + 1];
|
||||
expressions[0] = groupBy;
|
||||
for (int i=0; i < args.length; i++) {
|
||||
expressions[i+1] = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Group> transform(Projectable projectable) {
|
||||
final LinkedHashMap<Object, Group> groups = new LinkedHashMap<Object, Group>();
|
||||
|
||||
CloseableIterator<Object[]> iter = projectable.iterate(expressions);
|
||||
try {
|
||||
while (iter.hasNext()) {
|
||||
Object[] row = iter.next();
|
||||
Object groupBy = row[0];
|
||||
// groups.values() should return Collection<GTuple> instead of Collection<? extends GTuple>
|
||||
GTupleImpl group = (GTupleImpl) groups.get(groupBy);
|
||||
if (group == null) {
|
||||
group = new GTupleImpl();
|
||||
groups.put(groupBy, group);
|
||||
}
|
||||
group.add(row);
|
||||
}
|
||||
} finally {
|
||||
iter.close();
|
||||
}
|
||||
return groups.values();
|
||||
}
|
||||
|
||||
private int indexOf(Expression<?> expr) {
|
||||
for (int i=0; i < expressions.length; i++) {
|
||||
if (expressions[i].equals(expr)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private class GTupleImpl implements Group {
|
||||
|
||||
private final List<Object[]> values = new ArrayList<Object[]>();
|
||||
|
||||
@Override
|
||||
public <T> T get(int index, Class<T> type) {
|
||||
return (T) values.get(0)[index];
|
||||
}
|
||||
|
||||
private void add(Object[] row) {
|
||||
this.values.add(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(Expression<T> expr) {
|
||||
int index = indexOf(expr);
|
||||
return index != -1 ? (T) values.get(0)[index] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getList(int index, Class<T> type) {
|
||||
List<T> list = new ArrayList<T>(values.size());
|
||||
for (Object[] o : values) {
|
||||
list.add((T) o[index]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getList(Expression<T> expr) {
|
||||
int index = indexOf(expr);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
} else {
|
||||
List<T> list = new ArrayList<T>(values.size());
|
||||
for (Object[] o : values) {
|
||||
list.add((T) o[index]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return values.size();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Mysema Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
package com.mysema.query.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.mysema.commons.lang.CloseableIterator;
|
||||
import com.mysema.commons.lang.EmptyCloseableIterator;
|
||||
import com.mysema.query.Projectable;
|
||||
import com.mysema.query.SearchResults;
|
||||
import com.mysema.query.types.Expression;
|
||||
|
||||
public class AbstractProjectable implements Projectable {
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countDistinct() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return 0 < count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean notExists() {
|
||||
return !exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableIterator<Object[]> iterate(Expression<?> first,
|
||||
Expression<?> second, Expression<?>... rest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableIterator<Object[]> iterate(Expression<?>[] args) {
|
||||
return new EmptyCloseableIterator<Object[]>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> CloseableIterator<RT> iterate(Expression<RT> projection) {
|
||||
return new EmptyCloseableIterator<RT>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableIterator<Object[]> iterateDistinct(Expression<?> first,
|
||||
Expression<?> second, Expression<?>... rest) {
|
||||
return new EmptyCloseableIterator<Object[]>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableIterator<Object[]> iterateDistinct(Expression<?>[] args) {
|
||||
return new EmptyCloseableIterator<Object[]>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> CloseableIterator<RT> iterateDistinct(Expression<RT> projection) {
|
||||
return new EmptyCloseableIterator<RT>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object[]> list(Expression<?> first, Expression<?> second,
|
||||
Expression<?>... rest) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object[]> list(Expression<?>[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> List<RT> list(Expression<RT> projection) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object[]> listDistinct(Expression<?> first,
|
||||
Expression<?> second, Expression<?>... rest) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object[]> listDistinct(Expression<?>[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> List<RT> listDistinct(Expression<RT> projection) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> SearchResults<RT> listResults(Expression<RT> projection) {
|
||||
return new SearchResults<RT>(Collections.<RT>emptyList(), null, null, 0l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> SearchResults<RT> listDistinctResults(Expression<RT> projection) {
|
||||
return new SearchResults<RT>(Collections.<RT>emptyList(), null, null, 0l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Map<K, V> map(Expression<K> key, Expression<V> value) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] singleResult(Expression<?> first, Expression<?> second,
|
||||
Expression<?>... rest) {
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] singleResult(Expression<?>[] args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> RT singleResult(Expression<RT> projection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] uniqueResult(Expression<?> first, Expression<?> second,
|
||||
Expression<?>... rest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] uniqueResult(Expression<?>[] args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RT> RT uniqueResult(Expression<RT> projection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package com.mysema.query.support;
|
||||
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mysema.commons.lang.CloseableIterator;
|
||||
import com.mysema.commons.lang.IteratorAdapter;
|
||||
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 GroupByTest {
|
||||
|
||||
private final NumberExpression<Integer> postId = new NumberPath(Integer.class, null, "postId");
|
||||
|
||||
private final StringExpression postName = new StringPath(null, "postName");
|
||||
|
||||
private final NumberExpression<Integer> commentId = new NumberPath(Integer.class, null, "commentId");
|
||||
|
||||
/**
|
||||
* <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 Group_By() {
|
||||
Collection<Group> results = new GroupBy(postId, postName, commentId).transform(new AbstractProjectable(){
|
||||
public CloseableIterator<Object[]> iterate(Expression<?>[] args) {
|
||||
assertEquals(postId, args[0]);
|
||||
assertEquals(postName, args[1]);
|
||||
assertEquals(commentId, args[2]);
|
||||
|
||||
return iterator(
|
||||
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<Group> iter = results.iterator();
|
||||
|
||||
Group g = iter.next();
|
||||
assertEquals(toInt(1), g.get(postId));
|
||||
assertEquals("post 1", g.get(postName));
|
||||
assertEquals(3, g.size());
|
||||
List<Integer> comments = g.getList(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));
|
||||
assertEquals(2, g.size());
|
||||
comments = g.getList(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));
|
||||
assertEquals(1, g.size());
|
||||
comments = g.getList(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));
|
||||
assertEquals(2, g.size());
|
||||
comments = g.getList(commentId);
|
||||
assertEquals(toInt(7), comments.get(0));
|
||||
assertEquals(toInt(8), comments.get(1));
|
||||
}
|
||||
|
||||
private Integer toInt(int i) {
|
||||
return Integer.valueOf(i);
|
||||
}
|
||||
|
||||
private static Object[] row(Object... row) {
|
||||
return row;
|
||||
}
|
||||
|
||||
private static <T> CloseableIterator<T> iterator(Object[]... rows) {
|
||||
return new IteratorAdapter(Arrays.asList(rows).iterator());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user