MongoDB: support $elemMatch feature #115

Closed
rocketraman opened this Issue Mar 10, 2012 · 12 comments

Comments

Projects
None yet
2 participants
@rocketraman
Contributor

rocketraman commented Mar 10, 2012

In MongoDB, if one wishes to apply multiple criteria to embedded documents within an array, one has to use the $elemMatch feature. See "Matching with $elemMatch" at http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29.

It would be cool if QueryDSL was able to support this query notation.

A good example is given at the bottom of the page above:

// Document 1
{ "foo" : [
      {
        "shape" : "square",
        "color" : "purple",
        "thick" : false
      },
      {
        "shape" : "circle",
        "color" : "red",
        "thick" : true
      }
] }


// Document 2
{ "foo" : [
      {
        "shape" : "square",
        "color" : "red",
        "thick" : true
      },
      {
        "shape" : "circle",
        "color" : "purple",
        "thick" : false
      }
] }

In order to query for a document containing a purple square, the mongodb query is:

db.foo.find({foo: {"$elemMatch": {shape: "square", color: "purple"}}})

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Mar 10, 2012

Member

This query notation can't be supported directly, but we could figure out what would be an intuitive Querydsl way to express conditions that map in to $elemMatch

The collection.any() doesn't work since it creates always a new alias

The problem with join(...) is that implies maybe something like a reference in Mongodb.

Member

timowest commented Mar 10, 2012

This query notation can't be supported directly, but we could figure out what would be an intuitive Querydsl way to express conditions that map in to $elemMatch

The collection.any() doesn't work since it creates always a new alias

The problem with join(...) is that implies maybe something like a reference in Mongodb.

@rocketraman

This comment has been minimized.

Show comment
Hide comment
@rocketraman

rocketraman Mar 11, 2012

Contributor

Yeah, I'm not sure what would fit best into the existing DSL.

Perhaps something like: joinNested(...) ?

Contributor

rocketraman commented Mar 11, 2012

Yeah, I'm not sure what would fit best into the existing DSL.

Perhaps something like: joinNested(...) ?

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Mar 11, 2012

Member

It appears the constraints also need to be in one place, so maybe something like this

joinNested(doc.foo, foo).on(foo.shape.eq("square"), foo.color.eq("purple"))

This should be quite transparent, but is it intuitive?

Member

timowest commented Mar 11, 2012

It appears the constraints also need to be in one place, so maybe something like this

joinNested(doc.foo, foo).on(foo.shape.eq("square"), foo.color.eq("purple"))

This should be quite transparent, but is it intuitive?

@rocketraman

This comment has been minimized.

Show comment
Hide comment
@rocketraman

rocketraman Mar 11, 2012

Contributor
  1. joinNested is a bit confusing. How about anyEmbedded, which seems closer to the similar any syntax used by collections.
  2. I guess the second parameter to joinNested/anyEmbedded is an alias? If the user needs to specify the typed fields from the Q-class, is the alias useful?

How about this:

anyEmbedded(doc.foo).on(doc.foo.shape.eq("square"), doc.foo.color.eq("purple"))

(where I guess doc is a static import on QDoc)

Contributor

rocketraman commented Mar 11, 2012

  1. joinNested is a bit confusing. How about anyEmbedded, which seems closer to the similar any syntax used by collections.
  2. I guess the second parameter to joinNested/anyEmbedded is an alias? If the user needs to specify the typed fields from the Q-class, is the alias useful?

How about this:

anyEmbedded(doc.foo).on(doc.foo.shape.eq("square"), doc.foo.color.eq("purple"))

(where I guess doc is a static import on QDoc)

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Mar 11, 2012

Member

I guess an alias is needed, because foo is a Collection typed property.

Maybe

QDoc doc = QDoc.doc;
// where doc.foo property is of type Collection<Foo>
anyEmbedded(doc.foo, foo).on(foo.shape.eq("square"), foo.color.eq("purple"))

or

anyEmbedded(doc.foo).on(doc.foo.any().shape.eq("square"), doc.foo.any().eq("purple"))
Member

timowest commented Mar 11, 2012

I guess an alias is needed, because foo is a Collection typed property.

Maybe

QDoc doc = QDoc.doc;
// where doc.foo property is of type Collection<Foo>
anyEmbedded(doc.foo, foo).on(foo.shape.eq("square"), foo.color.eq("purple"))

or

anyEmbedded(doc.foo).on(doc.foo.any().shape.eq("square"), doc.foo.any().eq("purple"))
@rocketraman

This comment has been minimized.

Show comment
Hide comment
@rocketraman

rocketraman Mar 11, 2012

Contributor

The first of those seems much clearer to me -- the second seems to have too many redundant "any"'s. However, in the first, where does the foo variable come from in the parameters to the on method?

anyEmbedded(doc.foo, foo).on(foo.shape.eq("square"), foo.color.eq("purple"))
                             ^^^                     ^^^
Contributor

rocketraman commented Mar 11, 2012

The first of those seems much clearer to me -- the second seems to have too many redundant "any"'s. However, in the first, where does the foo variable come from in the parameters to the on method?

anyEmbedded(doc.foo, foo).on(foo.shape.eq("square"), foo.color.eq("purple"))
                             ^^^                     ^^^
@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Mar 11, 2012

Member

I also prefer the first one, the second was just an example how it would look like without extra variables. The used variables are

QDoc doc = QDoc.doc;
QFoo foo = QFoo.foo;

You can't used QDoc.doc.foo directly, since it is Collection typed. At least if I understand the elemMatch semantics correctly.

Member

timowest commented Mar 11, 2012

I also prefer the first one, the second was just an example how it would look like without extra variables. The used variables are

QDoc doc = QDoc.doc;
QFoo foo = QFoo.foo;

You can't used QDoc.doc.foo directly, since it is Collection typed. At least if I understand the elemMatch semantics correctly.

@rocketraman

This comment has been minimized.

Show comment
Hide comment
@rocketraman

rocketraman Mar 11, 2012

Contributor

Ok, that makes sense.

Contributor

rocketraman commented Mar 11, 2012

Ok, that makes sense.

timowest added a commit that referenced this issue Mar 25, 2012

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Mar 25, 2012

Member

Here is a related test case:

@Test
public void ElemMatch() {
    // { "addresses" : { "$elemMatch" : { "street" : "Aakatu1"}}}
    assertEquals(1, query().anyEmbedded(user.addresses, address).on(address.street.eq("Aakatu1")).count());
    // { "addresses" : { "$elemMatch" : { "street" : "Aakatu1" , "postCode" : "00100"}}}
    assertEquals(1, query().anyEmbedded(user.addresses, address).on(address.street.eq("Aakatu1"), address.postCode.eq("00100")).count());
    // { "addresses" : { "$elemMatch" : { "street" : "akatu"}}}
    assertEquals(0, query().anyEmbedded(user.addresses, address).on(address.street.eq("akatu")).count());
    // { "addresses" : { "$elemMatch" : { "street" : "Aakatu1" , "postCode" : "00200"}}}
    assertEquals(0, query().anyEmbedded(user.addresses, address).on(address.street.eq("Aakatu1"), address.postCode.eq("00200")).count());
}
Member

timowest commented Mar 25, 2012

Here is a related test case:

@Test
public void ElemMatch() {
    // { "addresses" : { "$elemMatch" : { "street" : "Aakatu1"}}}
    assertEquals(1, query().anyEmbedded(user.addresses, address).on(address.street.eq("Aakatu1")).count());
    // { "addresses" : { "$elemMatch" : { "street" : "Aakatu1" , "postCode" : "00100"}}}
    assertEquals(1, query().anyEmbedded(user.addresses, address).on(address.street.eq("Aakatu1"), address.postCode.eq("00100")).count());
    // { "addresses" : { "$elemMatch" : { "street" : "akatu"}}}
    assertEquals(0, query().anyEmbedded(user.addresses, address).on(address.street.eq("akatu")).count());
    // { "addresses" : { "$elemMatch" : { "street" : "Aakatu1" , "postCode" : "00200"}}}
    assertEquals(0, query().anyEmbedded(user.addresses, address).on(address.street.eq("Aakatu1"), address.postCode.eq("00200")).count());
}
@rocketraman

This comment has been minimized.

Show comment
Hide comment
@rocketraman

rocketraman Mar 27, 2012

Contributor

Sweet.

Contributor

rocketraman commented Mar 27, 2012

Sweet.

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Apr 20, 2012

Member

Released in 2.5.0

Member

timowest commented Apr 20, 2012

Released in 2.5.0

@timowest timowest closed this Apr 20, 2012

@rocketraman

This comment has been minimized.

Show comment
Hide comment
@rocketraman

rocketraman Nov 22, 2012

Contributor

Spring Data MongoDB uses the javax.persistence.Embedded annotation... I notice the test case is using the Morphia annotation. Is it a change in QueryDSL to recognize the javax.persistence.Embedded annotation as well?

Contributor

rocketraman commented Nov 22, 2012

Spring Data MongoDB uses the javax.persistence.Embedded annotation... I notice the test case is using the Morphia annotation. Is it a change in QueryDSL to recognize the javax.persistence.Embedded annotation as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment