ResultTransformer interface and implementation for grouping of projected rows

This commit is contained in:
Samppa Saarela 2011-09-14 11:55:34 +03:00
parent d33e0729bb
commit f35479759d
5 changed files with 451 additions and 0 deletions

View File

@ -0,0 +1,8 @@
package com.mysema.query;
public interface ResultTransformer<T> {
public T transform(Projectable projectable);
}

View File

@ -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();
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}