#113 first sketch of Querydsl mongodb join/ref support

This commit is contained in:
Timo Westkämper 2012-04-03 22:15:00 +03:00
parent 354ba024af
commit 48fa1a5a15
3 changed files with 160 additions and 55 deletions

View File

@ -37,10 +37,12 @@ import com.mysema.query.SimpleProjectable;
import com.mysema.query.SimpleQuery;
import com.mysema.query.support.QueryMixin;
import com.mysema.query.types.Expression;
import com.mysema.query.types.ExpressionUtils;
import com.mysema.query.types.Operation;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.ParamExpression;
import com.mysema.query.types.Path;
import com.mysema.query.types.PathImpl;
import com.mysema.query.types.Predicate;
/**
@ -52,6 +54,9 @@ import com.mysema.query.types.Predicate;
*/
public abstract class MongodbQuery<K> implements SimpleQuery<MongodbQuery<K>>, SimpleProjectable<K> {
@SuppressWarnings("serial")
private static class NoResults extends RuntimeException {}
private final MongodbSerializer serializer;
private final QueryMixin<MongodbQuery<K>> queryMixin;
@ -78,34 +83,61 @@ public abstract class MongodbQuery<K> implements SimpleQuery<MongodbQuery<K>>, S
protected abstract DBCollection getCollection(Class<?> type);
@Override
public boolean exists() {
QueryMetadata metadata = queryMixin.getMetadata();
if (!metadata.getJoins().isEmpty()) {
Predicate extraFilter = null;
List<JoinExpression> joins = metadata.getJoins();
for (int i = joins.size() - 1; i >= 0; i--) {
JoinExpression join = joins.get(i);
Expression<?> source = ((Operation<?>)join.getTarget()).getArg(0);
Class<?> target = ((Operation<?>)join.getTarget()).getArg(1).getType();
List<DBObject> ids = getIds(target, join.getCondition());
}
} else {
return collection.findOne(createQuery(metadata.getWhere())) != null;
public boolean exists() {
try {
QueryMetadata metadata = queryMixin.getMetadata();
Predicate filter = createFilter(metadata);
return collection.findOne(createQuery(filter)) != null;
} catch (NoResults ex) {
return false;
}
}
protected List<DBObject> getIds(Class<?> target, Predicate condition) {
@Nullable
protected Predicate createFilter(QueryMetadata metadata) {
Predicate filter;
if (!metadata.getJoins().isEmpty()) {
filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata));
} else {
filter = metadata.getWhere();
}
return filter;
}
@Nullable
protected Predicate createJoinFilter(QueryMetadata metadata) {
Predicate extraFilter = null;
List<JoinExpression> joins = metadata.getJoins();
for (int i = joins.size() - 1; i >= 0; i--) {
JoinExpression join = joins.get(i);
Expression<?> source = ((Operation<?>)join.getTarget()).getArg(0);
Class<?> target = ((Operation<?>)join.getTarget()).getArg(1).getType();
Predicate filter = ExpressionUtils.allOf(join.getCondition(), extraFilter);
List<Object> ids = getIds(target, filter);
if (ids.isEmpty()) {
throw new NoResults();
}
Path path = new PathImpl<String>(String.class, (Path)source, "$id");
extraFilter = ExpressionUtils.in(path, ids);
}
return extraFilter;
}
protected List<Object> getIds(Class<?> target, Predicate condition) {
DBCollection collection = getCollection(target);
DBCursor cursor = createCursor(condition, QueryModifiers.EMPTY, Collections.<OrderSpecifier<?>>emptyList());
// TODO : fetch only ids
DBCursor cursor = createCursor(collection, condition, QueryModifiers.EMPTY, Collections.<OrderSpecifier<?>>emptyList());
if (cursor.hasNext()) {
List<Object> ids = new ArrayList<Object>(cursor.count());
for (DBObject obj : cursor) {
ids.add(obj.containsField("id") ? obj.get("id") : obj.get("_id"));
}
return ids;
} else {
return Collections.emptyList();
}
}
@Override
public boolean notExists() {
return !exists();
@ -177,20 +209,25 @@ public abstract class MongodbQuery<K> implements SimpleQuery<MongodbQuery<K>>, S
@Override
public List<K> list() {
DBCursor cursor = createCursor();
List<K> results = new ArrayList<K>(cursor.size());
for (DBObject dbObject : cursor) {
results.add(transformer.transform(dbObject));
}
return results;
try {
DBCursor cursor = createCursor();
List<K> results = new ArrayList<K>(cursor.size());
for (DBObject dbObject : cursor) {
results.add(transformer.transform(dbObject));
}
return results;
} catch (NoResults ex) {
return Collections.emptyList();
}
}
protected DBCursor createCursor() {
QueryMetadata metadata = queryMixin.getMetadata();
return createCursor(metadata.getWhere(), metadata.getModifiers(), metadata.getOrderBy());
Predicate filter = createFilter(metadata);
return createCursor(collection, filter, metadata.getModifiers(), metadata.getOrderBy());
}
protected DBCursor createCursor(@Nullable Predicate where, QueryModifiers modifiers,
protected DBCursor createCursor(DBCollection collection, @Nullable Predicate where, QueryModifiers modifiers,
List<OrderSpecifier<?>> orderBy) {
DBCursor cursor = collection.find(createQuery(where));
if (modifiers.getLimit() != null){
@ -212,40 +249,52 @@ public abstract class MongodbQuery<K> implements SimpleQuery<MongodbQuery<K>>, S
@Override
public K singleResult() {
DBCursor c = createCursor().limit(1);
if (c.hasNext()){
return transformer.transform(c.next());
} else {
try {
DBCursor c = createCursor().limit(1);
if (c.hasNext()){
return transformer.transform(c.next());
} else {
return null;
}
} catch (NoResults ex) {
return null;
}
}
}
@Override
public K uniqueResult() {
Long limit = queryMixin.getMetadata().getModifiers().getLimit();
if (limit == null){
limit = 2l;
}
DBCursor c = createCursor().limit(limit.intValue());
if (c.hasNext()){
K rv = transformer.transform(c.next());
if (c.hasNext()){
throw new NonUniqueResultException();
try {
Long limit = queryMixin.getMetadata().getModifiers().getLimit();
if (limit == null){
limit = 2l;
}
return rv;
} else {
DBCursor c = createCursor().limit(limit.intValue());
if (c.hasNext()){
K rv = transformer.transform(c.next());
if (c.hasNext()){
throw new NonUniqueResultException();
}
return rv;
} else {
return null;
}
} catch (NoResults ex) {
return null;
}
}
}
@Override
public SearchResults<K> listResults() {
long total = count();
if (total > 0l){
return new SearchResults<K>(list(), queryMixin.getMetadata().getModifiers(), total);
} else {
try {
long total = count();
if (total > 0l){
return new SearchResults<K>(list(), queryMixin.getMetadata().getModifiers(), total);
} else {
return SearchResults.emptyResults();
}
} catch (NoResults ex) {
return SearchResults.emptyResults();
}
}
}
@Override
@ -255,7 +304,12 @@ public abstract class MongodbQuery<K> implements SimpleQuery<MongodbQuery<K>>, S
@Override
public long count() {
return collection.count(createQuery(queryMixin.getMetadata().getWhere()));
try {
Predicate filter = createFilter(queryMixin.getMetadata());
return collection.count(createQuery(filter));
} catch (NoResults ex) {
return 0l;
}
}
@Override

View File

@ -16,9 +16,11 @@ package com.mysema.query.mongodb;
import static java.util.Arrays.asList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -32,6 +34,8 @@ import org.junit.Test;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.Morphia;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.SearchResults;
import com.mysema.query.mongodb.domain.Address;
@ -50,9 +54,11 @@ import com.mysema.query.types.path.StringPath;
public class MongodbQueryTest {
private final Mongo mongo;
private final Morphia morphia;
private final Datastore ds;
private final String dbname = "testdb";
private final Morphia morphia = new Morphia().map(User.class).map(Item.class);
private final Datastore ds = morphia.createDatastore(dbname);
private final QUser user = QUser.user;
private final QItem item = QItem.item;
private final QAddress address = QAddress.address;
@ -60,8 +66,15 @@ public class MongodbQueryTest {
User u1, u2, u3, u4;
City tampere, helsinki;
public MongodbQueryTest() throws UnknownHostException, MongoException {
mongo = new Mongo();
morphia = new Morphia().map(User.class).map(Item.class);
ds = morphia.createDatastore(mongo, dbname, null, null);
}
@Before
public void before() {
public void before() throws UnknownHostException, MongoException {
ds.delete(ds.createQuery(Item.class));
ds.delete(ds.createQuery(User.class));
tampere = new City("Tampere", 61.30, 23.50);
@ -354,10 +367,43 @@ public class MongodbQueryTest {
@Test
public void Join() {
User friend = new User();
friend.setFirstName("Max");
User friend1 = new User("Max", null);
User friend2 = new User("Jack", null);
User friend3 = new User("Bob", null);
ds.save(friend1, friend2, friend3);
User user1 = new User("Jane", null, friend1);
User user2 = new User("Mary", null, user1);
User user3 = new User("Ann", null, friend3);
ds.save(user1, user2, user3);
QUser friend = new QUser("friend");
// count
assertEquals(1, where().join(user.friend(), friend).on(friend.firstName.eq("Max")).count());
assertEquals(1, where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Max")).count());
assertEquals(0, where(user.firstName.eq("Mary")).join(user.friend(), friend).on(friend.firstName.eq("Max")).count());
assertEquals(0, where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Jack")).count());
// exists
assertTrue(where().join(user.friend(), friend).on(friend.firstName.eq("Max")).exists());
assertTrue(where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Max")).exists());
assertFalse(where(user.firstName.eq("Mary")).join(user.friend(), friend).on(friend.firstName.eq("Max")).exists());
assertFalse(where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Jack")).exists());
// list
assertEquals(1, where().join(user.friend(), friend).on(friend.firstName.eq("Max")).list().size());
assertEquals(1, where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Max")).list().size());
assertEquals(0, where(user.firstName.eq("Mary")).join(user.friend(), friend).on(friend.firstName.eq("Max")).list().size());
assertEquals(0, where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Jack")).list().size());
// single
assertEquals("Jane", where().join(user.friend(), friend).on(friend.firstName.eq("Max")).singleResult().getFirstName());
assertEquals("Jane", where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Max")).singleResult().getFirstName());
assertNull(where(user.firstName.eq("Mary")).join(user.friend(), friend).on(friend.firstName.eq("Max")).singleResult());
assertNull(where(user.firstName.eq("Jane")).join(user.friend(), friend).on(friend.firstName.eq("Jack")).singleResult());
}
//TODO
// - test dates
// - test with empty values and nulls

View File

@ -56,6 +56,11 @@ public class User {
public User() {
}
public User(String firstName, String lastName, User friend) {
this(firstName, lastName);
this.friend = friend;
}
public User(String firstName, String lastName) {
this.firstName = firstName; this.lastName = lastName;
this.created = new Date();