From 48fa1a5a15b7cd662706ac6cc5207df447ef8450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Westk=C3=A4mper?= Date: Tue, 3 Apr 2012 22:15:00 +0300 Subject: [PATCH] #113 first sketch of Querydsl mongodb join/ref support --- .../mysema/query/mongodb/MongodbQuery.java | 152 ++++++++++++------ .../query/mongodb/MongodbQueryTest.java | 58 ++++++- .../com/mysema/query/mongodb/domain/User.java | 5 + 3 files changed, 160 insertions(+), 55 deletions(-) diff --git a/querydsl-mongodb/src/main/java/com/mysema/query/mongodb/MongodbQuery.java b/querydsl-mongodb/src/main/java/com/mysema/query/mongodb/MongodbQuery.java index 77deab725..77cd79ac1 100644 --- a/querydsl-mongodb/src/main/java/com/mysema/query/mongodb/MongodbQuery.java +++ b/querydsl-mongodb/src/main/java/com/mysema/query/mongodb/MongodbQuery.java @@ -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 implements SimpleQuery>, SimpleProjectable { + @SuppressWarnings("serial") + private static class NoResults extends RuntimeException {} + private final MongodbSerializer serializer; private final QueryMixin> queryMixin; @@ -78,34 +83,61 @@ public abstract class MongodbQuery implements SimpleQuery>, S protected abstract DBCollection getCollection(Class type); @Override - public boolean exists() { - QueryMetadata metadata = queryMixin.getMetadata(); - if (!metadata.getJoins().isEmpty()) { - Predicate extraFilter = null; - List 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 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 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 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 ids = getIds(target, filter); + if (ids.isEmpty()) { + throw new NoResults(); + } + Path path = new PathImpl(String.class, (Path)source, "$id"); + extraFilter = ExpressionUtils.in(path, ids); + } + return extraFilter; + } + + protected List getIds(Class target, Predicate condition) { DBCollection collection = getCollection(target); - DBCursor cursor = createCursor(condition, QueryModifiers.EMPTY, Collections.>emptyList()); + // TODO : fetch only ids + DBCursor cursor = createCursor(collection, condition, QueryModifiers.EMPTY, Collections.>emptyList()); if (cursor.hasNext()) { - + List ids = new ArrayList(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 implements SimpleQuery>, S @Override public List list() { - DBCursor cursor = createCursor(); - List results = new ArrayList(cursor.size()); - for (DBObject dbObject : cursor) { - results.add(transformer.transform(dbObject)); - } - return results; + try { + DBCursor cursor = createCursor(); + List results = new ArrayList(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> orderBy) { DBCursor cursor = collection.find(createQuery(where)); if (modifiers.getLimit() != null){ @@ -212,40 +249,52 @@ public abstract class MongodbQuery implements SimpleQuery>, 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 listResults() { - long total = count(); - if (total > 0l){ - return new SearchResults(list(), queryMixin.getMetadata().getModifiers(), total); - } else { + try { + long total = count(); + if (total > 0l){ + return new SearchResults(list(), queryMixin.getMetadata().getModifiers(), total); + } else { + return SearchResults.emptyResults(); + } + } catch (NoResults ex) { return SearchResults.emptyResults(); - } + } } @Override @@ -255,7 +304,12 @@ public abstract class MongodbQuery implements SimpleQuery>, 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 diff --git a/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/MongodbQueryTest.java b/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/MongodbQueryTest.java index ee2f22144..b9e03bc49 100644 --- a/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/MongodbQueryTest.java +++ b/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/MongodbQueryTest.java @@ -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 diff --git a/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/domain/User.java b/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/domain/User.java index d580f0dfd..8aebacf45 100644 --- a/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/domain/User.java +++ b/querydsl-mongodb/src/test/java/com/mysema/query/mongodb/domain/User.java @@ -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();