6

The title might not be super clear, here the problem

I'm executing an update in this form:

db.poi.update({
  _id: ObjectId("50f40cd052187a491707053b"),
  "votes.userid": {
    "$ne": "50f5460d5218fe9d1e2c7b4f"
  }
},
{
  $push: {
    votes: {
      "userid": "50f5460d5218fe9d1e2c7b4f", 
      "value": 1
    }
  },
  $inc: { "score":1 }
})

To insert a document in an array only if there isn't one with the same userid (workaround because unique indexes don't work on arrays). The code works fine from mongo console. From my application I'm using this:

@Override
public void vote(String id, Vote vote) {
    Query query = new Query(Criteria.where("_id").is(id).and("votes.userid").ne(vote.getUserid()));
    Update update = new Update().inc("score", vote.getValue()).push("votes", vote);
    mongoOperations.updateFirst(query, update, Poi.class);
}

This works fine if as "userid" I use a String that can't be a mongo ObjectId, but if I use the string in the example, the query executed translates like this (from mongosniff):

update  flags:0 q:{ _id: ObjectId('50f40cd052187a491707053b'), votes.userid: { $ne: ObjectId('50f5460d5218fe9d1e2c7b4f') } } o:{ $inc: { score: 1 }, $push: { votes: { userid: "50f5460d5218fe9d1e2c7b4f", value: 1 } } }

The string is now an Objectid. Is this a bug? BasicQuery do the same thing. The only other solution I see is to use ObjectId instead of String for all classes ids.

Any thoughts?

UPDATE:

This is the Vote class

public class Vote {
  private String userid;
  private int value;
}

This is the User class

@Document
public class User {

  @Id
  private String id;
  private String username;
}

This is the class and mongo document where I'm doing this update

@Document
public class MyClass {

  @Id
  private String id;
  @Indexed
  private String name;
  int score
  private Set<Vote>votes = new HashSet<Vote>();
}

As Json

{
  "_id" : ObjectId("50f40cd052187a491707053b"),
  "name" : "Test",
  "score" : 12,
  "votes" : [
    {
      "userid" : "50f5460d5218fe9d1e2c7b4f",
      "value" : 1
    }
  ]
}

Userid in votes.userid is pushed as String, but the same String is compared as an ObjectId in the $ne

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
alex
  • 3,412
  • 2
  • 28
  • 36
  • 1
    It seems that you store `String` values that can be `ObjectId`s internally. What's the reason for this? The `String`-to-`ObjectId` conversion is present to be able to use `String` in your Java objects and still have `ObjectId`s in the database. – Oliver Drotbohm Jan 15 '13 at 21:06
  • Maybe I'm missing something then. I'm using Strings as _id for the User objects; my Vote objects have a userid String field that I use as reference to the User _id. The votes "field" in the db is an arrays of documents. When I $push a vote.userid in this array, I have a String in the db, not an ObjectId. I'll update my post with some code to make it more clear. – alex Jan 15 '13 at 21:14
  • You don't say what version you're using of Spring-data-mongodb. I've recently been bitten by the reverse of this when upgrading from 1.0.3 to 1.1.0, since they seem to have taken away this automatic conversion. I was bitten because I'd exported as JSON and then re-imported, leaving me with "_id" : { "$id" : "4ee76418f3e1450f11000000" }, which gives me org.springframework.data.mapping.model.MappingException: No mapping metadata found for java.lang.String. I don't think you can have it both ways – Mick Sear Jul 03 '13 at 15:16
  • It was Spring Data Mongodb 1.2.0 – alex Jul 04 '13 at 08:31
  • I face similar issue. If I use same string in high level document, the comparison works. But If I put this in nested object, then it try to do ObjectId comparison. Very wierd. Otherwise also, the behavior should be same while inserting / querying. Must be a BUG. – Poorna Subhash Dec 11 '14 at 06:34

1 Answers1

3

It seems to me the problem can be described like this: if you use String in your classes in place of an ObjectId, if you want to use those ids as references (no dbrefs) in other documents (and embedded documents), they are pushed as String (it's ok because they are Strings). It's fine because spring data can map them again to objectid, but it's not fine if you do a query like the one I mentioned; the field is converted to an objectid in the comparison (the $ne operator in this case) but is considered as a string in the embedded document. So, to wrap up, in my opinion the $ne operator in this case should consider the field a String.

My solution was to write a custom converter to store the String as an objectid in the documents where the id is a reference

public class VoteWriteConverter implements Converter<Vote, DBObject> {

  @Override
  public DBObject convert(Vote vote) {
    DBObject dbo = new BasicDBObject();
    dbo.put("userid", new ObjectId(vote.getUserid()));
    dbo.put("value", vote.getValue());
    return dbo;
  }
}
Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
alex
  • 3,412
  • 2
  • 28
  • 36