diff --git a/morphia/src/main/java/org/mongodb/morphia/mapping/Mapper.java b/morphia/src/main/java/org/mongodb/morphia/mapping/Mapper.java index f36cf05938d..fd281ad2493 100644 --- a/morphia/src/main/java/org/mongodb/morphia/mapping/Mapper.java +++ b/morphia/src/main/java/org/mongodb/morphia/mapping/Mapper.java @@ -39,6 +39,8 @@ import org.mongodb.morphia.mapping.lazy.LazyProxyFactory; import org.mongodb.morphia.mapping.lazy.proxy.ProxiedEntityReference; import org.mongodb.morphia.mapping.lazy.proxy.ProxyHelper; +import org.mongodb.morphia.query.Query; +import org.mongodb.morphia.query.QueryImpl; import org.mongodb.morphia.query.ValidationException; import java.io.IOException; @@ -584,10 +586,9 @@ public DBObject toDBObject(final Object entity, final Map invo public Object toMongoObject(final MappedField mf, final MappedClass mc, final Object value) { Object mappedValue = value; - /* if (mf == null && mc != null && value != null) { - mappedValue = toMongoObject(mappedValue, false); - } else*/ - if (isAssignable(mf, value) || isEntity(mc)) { + if (value instanceof Query) { + mappedValue = ((QueryImpl) value).getQueryObject(); + } else if (isAssignable(mf, value) || isEntity(mc)) { //convert the value to Key (DBRef) if the field is @Reference or type is Key/DBRef, or if the destination class is an @Entity try { if (value instanceof Iterable) { diff --git a/morphia/src/main/java/org/mongodb/morphia/query/FieldEnd.java b/morphia/src/main/java/org/mongodb/morphia/query/FieldEnd.java index f32d3612a03..6f2a03d9248 100644 --- a/morphia/src/main/java/org/mongodb/morphia/query/FieldEnd.java +++ b/morphia/src/main/java/org/mongodb/morphia/query/FieldEnd.java @@ -138,9 +138,20 @@ public interface FieldEnd { * @return T * @mongodb.driver.manual reference/operator/query/elemMatch/ $elemMatch * @see MapperOptions + * @deprecated use {@link #elemMatch(Query)} instead */ + @Deprecated T hasThisElement(Object val); + /** + * Checks that a field matches the provided query definition + * + * @param query the query to find certain field values + * @return T + * @mongodb.driver.manual reference/operator/query/elemMatch/ $elemMatch + */ + T elemMatch(Query query); + /** * Checks that a field does not have the value listed. The options to store null/empty values apply here so to do partial matches on * embedded objects, pass a reference to a partially populated instance with only the values of interest set to the values to check. @@ -148,8 +159,10 @@ public interface FieldEnd { * @param val the value to check against * @return T * @mongodb.driver.manual reference/operator/query/elemMatch/ $elemMatch + * @deprecated use {@link #elemMatch(Query)} instead * @see MapperOptions */ + @Deprecated T doesNotHaveThisElement(Object val); /** diff --git a/morphia/src/main/java/org/mongodb/morphia/query/FieldEndImpl.java b/morphia/src/main/java/org/mongodb/morphia/query/FieldEndImpl.java index 9bfab5972a2..ce73d5aab22 100644 --- a/morphia/src/main/java/org/mongodb/morphia/query/FieldEndImpl.java +++ b/morphia/src/main/java/org/mongodb/morphia/query/FieldEndImpl.java @@ -145,6 +145,12 @@ public T hasThisElement(final Object val) { return addCriteria(FilterOperator.ELEMENT_MATCH, val, false); } + @Override + public T elemMatch(final Query query) { + Assert.parametersNotNull("query", query); + return addCriteria(FilterOperator.ELEMENT_MATCH, query, not); + } + @Override public T hasThisOne(final Object val) { return addCriteria(FilterOperator.EQUAL, val); diff --git a/morphia/src/main/java/org/mongodb/morphia/query/QueryImpl.java b/morphia/src/main/java/org/mongodb/morphia/query/QueryImpl.java index b6544f23ac5..1a658ca39dc 100644 --- a/morphia/src/main/java/org/mongodb/morphia/query/QueryImpl.java +++ b/morphia/src/main/java/org/mongodb/morphia/query/QueryImpl.java @@ -728,7 +728,7 @@ public DBCursor prepareCursor() { @Override public String toString() { - return String.format("{ %s %s }", getQueryObject(), projections == null ? "" : ", " + getFieldsObject()); + return String.format("{ query: %s %s }", getQueryObject(), projections == null ? "" : ", projection: " + getFieldsObject()); } /** diff --git a/morphia/src/test/java/org/mongodb/morphia/TestQuery.java b/morphia/src/test/java/org/mongodb/morphia/TestQuery.java index 5ce00fb1412..fe25cbad1d1 100644 --- a/morphia/src/test/java/org/mongodb/morphia/TestQuery.java +++ b/morphia/src/test/java/org/mongodb/morphia/TestQuery.java @@ -357,49 +357,88 @@ public void testElemMatchQuery() { .hasThisElement(new Keyword("Randy")) .get()); } + @Test + public void testComplexElemMatchQuery() { + Keyword oscar = new Keyword("Oscar", 42); + getDs().save(new PhotoWithKeywords(oscar, new Keyword("Jim", 12))); + assertNull(getDs().find(PhotoWithKeywords.class) + .field("keywords") + .elemMatch(getDs() + .createQuery(Keyword.class) + .filter("keyword = ", "Oscar") + .filter("score = ", 12)) + .get()); + + List keywords = getDs().find(PhotoWithKeywords.class) + .field("keywords") + .elemMatch(getDs() + .createQuery(Keyword.class) + .filter("score > ", 20) + .filter("score < ", 100)) + .asList(); + assertEquals(1, keywords.size()); + assertEquals(oscar, keywords.get(0).keywords.get(0)); + } @Test - public void testElemMatchQueryCanBeNegated() { + public void testElemMatchVariants() { final PhotoWithKeywords pwk1 = new PhotoWithKeywords(); final PhotoWithKeywords pwk2 = new PhotoWithKeywords("Kevin"); final PhotoWithKeywords pwk3 = new PhotoWithKeywords("Scott", "Joe", "Sarah"); final PhotoWithKeywords pwk4 = new PhotoWithKeywords(new Keyword("Scott", 14)); - getDs().save(pwk1, pwk2, pwk3, pwk4); - - Mapper mapper = getMorphia().getMapper(); - - List> keys = getDs().find(PhotoWithKeywords.class) - .field("keywords") - .hasThisElement(new Keyword("Scott")) - .asKeyList(); - - assertEquals(2, keys.size()); - assertFalse("because it doesn't have any keywords.", keys.contains(mapper.getKey(pwk1))); - assertFalse("because it doesn't have a matching keyword.", keys.contains(mapper.getKey(pwk2))); - assertTrue("because it has matching keyword.", keys.contains(mapper.getKey(pwk3))); - assertTrue("because it has matching keyword.", keys.contains(mapper.getKey(pwk4))); - - keys = getDs().find(PhotoWithKeywords.class) - .field("keywords") - .hasThisElement(new Keyword(14)) - .asKeyList(); - - assertEquals(1, keys.size()); - assertFalse(keys.contains(mapper.getKey(pwk1))); - assertFalse(keys.contains(mapper.getKey(pwk2))); - assertFalse(keys.contains(mapper.getKey(pwk3))); - assertTrue(keys.contains(mapper.getKey(pwk4))); - - keys = getDs().find(PhotoWithKeywords.class) - .field("keywords") - .doesNotHaveThisElement(new Keyword("Scott")) - .asKeyList(); - - assertEquals(2, keys.size()); - assertTrue("because it doesn't have any keywords.", keys.contains(mapper.getKey(pwk1))); - assertTrue("because it doesn't have a matching keyword.", keys.contains(mapper.getKey(pwk2))); - assertFalse("because it has matching keyword.", keys.contains(mapper.getKey(pwk3))); + Iterator> iterator = getDs().save(pwk1, pwk2, pwk3, pwk4).iterator(); + Key key1 = iterator.next(); + Key key2 = iterator.next(); + Key key3 = iterator.next(); + Key key4 = iterator.next(); + + validate(asList(key3, key4), asList(key1, key2), getDs().find(PhotoWithKeywords.class) + .field("keywords") + .hasThisElement(new Keyword("Scott")) + .asKeyList()); + + validate(asList(key3, key4), asList(key1, key2), getDs().find(PhotoWithKeywords.class) + .field("keywords") + .elemMatch(getDs() + .createQuery(Keyword.class) + .field("keyword").equal("Scott")) + .asKeyList()); + + validate(asList(key4), asList(key1, key2, key3), getDs().find(PhotoWithKeywords.class) + .field("keywords") + .hasThisElement(new Keyword(14)) + .asKeyList()); + + validate(asList(key4), asList(key1, key2, key3), getDs().find(PhotoWithKeywords.class) + .field("keywords") + .elemMatch(getDs() + .createQuery(Keyword.class) + .field("score").equal(14)) + .asKeyList()); + + validate(asList(key1, key2), asList(key3, key4), getDs().find(PhotoWithKeywords.class) + .field("keywords") + .doesNotHaveThisElement(new Keyword("Scott")) + .asKeyList()); + + validate(asList(key1, key2), asList(key3, key4), getDs().find(PhotoWithKeywords.class) + .field("keywords").not() + .elemMatch(getDs() + .createQuery(Keyword.class) + .field("keyword").equal("Scott")) + .asKeyList()); + } + + private void validate(final List> found, final List> notFound, + final List> keys) { + assertEquals(found.size(), keys.size()); + for (Key key : found) { + assertTrue(keys.contains(key)); + } + for (Key key : notFound) { + assertFalse(keys.contains(key)); + } } @Test @@ -1166,6 +1205,31 @@ public Keyword(final String k, final Integer score) { public Keyword(final Integer score) { this.score = score; } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Keyword)) { + return false; + } + + final Keyword keyword1 = (Keyword) o; + + if (keyword != null ? !keyword.equals(keyword1.keyword) : keyword1.keyword != null) { + return false; + } + return score != null ? score.equals(keyword1.score) : keyword1.score == null; + + } + + @Override + public int hashCode() { + int result = keyword != null ? keyword.hashCode() : 0; + result = 31 * result + (score != null ? score.hashCode() : 0); + return result; + } } public static class ContainsPhotoKey {