Mongo $elemMatch in Casbah - scala

I'm using Casbah 2.9.2
My mongodb schema looks like:
[ _id : "Triangle", Info : [ Color : "Red", Line : "Thin", UseID : "1", SourceId : "2" ] ]
I want to be able to write an update line that first checks if _id, Color and Line are together unique, if so update the UseID and SourceID otherwise create a new "Info" entry. This gets me exactly what I want from the command line:
db.shapes.update( { _id : 'Triangle', Info : { $not : { $elemMatch : { Color : 'Red', Line : 'Thick' } } } }, { $push : { Info : { Color : 'Red', Line : 'Thick', UseID : '2', SourceId : '3' } } }, true)
Giving me this entry:
[ _id : "Triangle", Info : [ { Color : "Red", Line : "Thin", UseID : "1", SourceId : "2" }, { Color : "Red", Line : "Thick", UseID : "2", SourceId : "3" } ] ]
However when I translate to Casbah for Scala it does not:
shapesCollection.update( { "_id" -> shape, "Info" $not { _ $elemMatch { "Color" -> color, "Line" -> line } } }, { $push -> { "Info" -> { "Color" -> color, "Line" -> line, "UseId" -> useId, "SourceId" -> srcId } } }, true )
The first complaint (from IntelliJ) is it wants all of the ','s to be ';'s which I feel is not correct, but in order to debug I oblige, which takes me to the second error saying:
"error: No Implicit view available from (java.lang.String, java.lang.String) => com.mongodb.casbah.query.Imports.DBObject. Error occurred in an application involving default arguments. "Info" $not { _ $elemMatch {"
So I guess the questions are, Can I even do the update I'm doing from the command line in Casbah? If so, how?
Thanks!

So for anyone that cares, first my update string was a little off, it would throw errors if the "Color" and "Line" didn't exist but the "_id" did. Basically it looks like $push with upsetter as true doesn't add to an existing "_id", so I was getting duplicate key errors as it tried to add the entry. But, that's moot, the real solution to what I wanted to do was to not use Casbah and instead use the MongoDB Java Driver. I was then able to get $elemMatch to do what I wanted.
Also this was probably part of my problem, since I went a different route I didn't get a chance to test it:
"If you really feel the need to use ++ with a mix of DSL and bare matches, we provide additive support for -> Tuple pairs. You should make the query operator calls first:"
val qMix = ("baz" -> 5) ++ ("foo" $gte 5) ++ ("x" -> "y")
/* error: value ++ is not a member of (java.lang.String, Int)
val qMix = ("baz" -> 5) ++ ("foo" $gte 5) ++ ("x" -> "y") */
The operator is chained against the result of DSL operators (which incidentally properly return a DBObject):
val qMix = ("foo" $gte 5) ++ ("baz" -> 5) ++ ("x" -> "y")
/* qMix: com.mongodb.casbah.commons.Imports.DBObject =
{ "foo" : { "$gte" : 5} , "baz" : 5 , "x" : "y"} */
From: http://api.mongodb.org/scala/casbah/current/tutorial.html

Related

In Reactivemongo Aggregation Framework, how to specify multiple keys in the group function "Max"?

This is sample data (expireDate is optional):
{"userId":"1","appId":"1","createdDate":10}
{"userId":"1","appId":"1","createdDate":5,"expireDate":30}
{"userId":"1","appId":"1","createdDate":12,"expireDate":20}
{"userId":"1","appId":"1","createdDate":12,"expireDate":5}
This is the aggregation function that I want to translate to reactivemongo aggregation framework:
db.collection_name.aggregate([
{
$match : {"userId" : "1"}
},
{
$group : {
"_id" : "$appId",
"docs" : {
$max : {
"createdDate" : "$createdDate",
"expireDate" : "$expireDate"
}
}
}
}
])
To run the aggregation function on the sample data (with mongo shell 3.2.9), the result is:
{ "_id" : "1", "docs" : { "createdDate" : 12, "expireDate" : 20 } }
When I try to convert this aggregation function to reactivemongo, I realize that the group function "Max" only takes one string as parameter, so I don't know how to put both "createdDate" and "expireDate" in it. Here is what I figure out so far:
col.aggregate(
Match(BSONDocument("userId" -> "1")),
List(Group(BSONString("$appId"))( "docs" -> Max("createdDate")))
)
Can anybody tell me how to add "expireDate" into "Max" function?
Note that I'm using reactivemongo 0.11, and upgrading to 0.12 is not an option.
You can use reactivemongo.api.commands.AggregationFramework.GroupFunction.apply to create a custom call to a group function. Here is the doc and here is the source where I found it.
GroupFunction("$max", BSONDocument("createdDate" -> "$createdDate", "expireDate" -> "$expireDate"))
Use this function instead of Max, so your code becomes:
col.aggregate(
Match(BSONDocument("userId" -> "1")),
List(Group(BSONString("$appId"))( "docs" -> GroupFunction("$max", BSONDocument("createdDate" -> "$createdDate", "expireDate" -> "$expireDate"))))
)
Don't forget to import col.BatchCommands.AggregationFramework.GroupFunction.
Of course you can define a more generic function which takes any BSONDocument as a parameter, if you want:
def BetterMax(doc: BSONDocument) = GroupFunction("$max", doc)

mongodb $elemMatch in query return all sub docs

db.aaa.insert({"_id":1, "m":[{"_id":1,"a":1},{"_id":2,"a":2}]})
db.aaa.find({"_id":1,"m":{$elemMatch:{"_id":1}}})
{ "_id" : 1, "m" : [ { "_id" : 1, "a" : 1 }, { "_id" : 2, "a" : 2 } ] }
Using $elemMatch as query operator, it return all sub docs in 'm' !! Strange!
Use it as project operator:
db.aaa.find({"_id":1},{"m":{$elemMatch:{"_id":1}}})
{ "_id" : 1, "m" : [ { "_id" : 1, "a" : 1 } ] }
This is OK. Following this logic, use it as query operator in update will change all sub docs in 'm'. So I do:
db.aaa.update({"_id":1,"m":{$elemMatch:{"_id":1}}},{$set:{"m.$.a":3}})
db.aaa.find()
{ "_id" : 1, "m" : [ { "_id" : 1, "a" : 3 }, { "_id" : 2, "a" : 2 } ] }
It works in manner of as second example(project operator). This really confuse me.
Give me a explain
It Isn't strange, it's how it works.
You are using $elemMatch to match an element within an array contained in your document. That means it mactches the "document" and not the "array element", so it does not just selectively display only the array element that was matched.
What you can do, and how you used it in with the $set operator, is use a positional $ operator to indicate the matched "position" from your query side:
db.aaa.find({"_id":1},{"m":{$elemMatch:{"_id":1}}},{ "m.$": 1 })
And that will show you only one element of the array. But it is of course *still an array in the result shown, and you cannot cast it to a different type.
The other part of the usage is that this will only match once. And only the first match will be assigned to the positional operator.
So perhaps the most succinct explaination is you matching the "document that contains" the properties of the sub-document your specified in your query, and not just the "sub-document" itself.
See the documentation for more:
http://docs.mongodb.org/manual/reference/operator/projection/positional/
http://docs.mongodb.org/manual/reference/operator/query/elemMatch/

MongoDB elemMatch subdocuments

I have the following data structure
{
"_id" : ObjectId("523331359245b5a07b903ccc"),
"a" : "a",
"b" : [
{
"c" : {
"_id" : ObjectId("5232b5090364678515db9a82"),
"d" : "d1"
}
},
{
"c" : {
"_id" : ObjectId("5232b5090364678515db9a83"),
"d" : "d2"
}
}
]
}
For the following queries, mongo returns
> db.test.find({b : {$elemMatch : {'c.d': 'd1'}}}).count();
1
> db.test.find({b : {$elemMatch : {c: {d: 'd1'}}}}).count();
0
Unfortunately, for the following statements
B b = new B();
C c = new C();
b.c = c;
b.c.d = "d1";
createQuery().field("b").hasThisElement(b).asList();
Morphia generates db.test.find({b : {$elemMatch : {c: {d: 'd1'}}}}) which returns 0 match.
Is this a mongo bug or a morphia bug? Is there any workaround for me to get the matched document?
Please note that in the real world practice, I have 2 conditions for elemMatch, hence I have to use "elemMatch", not "dot notation" match. The above is just to simplify my case for easy viewing.
I'm running Mongodb 2.4.6 and Morphia 1.2.3
Thanks!
It is too late, but maybe others can found it handy.
I found that solution https://groups.google.com/forum/#!topic/morphia/FlEjBoSqkhg
updateQuery.filter("b elem",
BasicDBObjectBuilder.start("c.d", "d1").get());

nested pull with scala mongodb casbah

lets say i have a simple object
{
"id":"xyz"
"answers" : [{
"name" : "Yes",
}, {
"name" : "No",
}]
}
I want to remove answer Yes from the array
I'm trying something like this without much luck:
import com.mongodb.casbah.MongoCollection
val searchObject = MongoDBObject("id"->"xyz");
getCollection().update(searchObject,$pull( "answers" -> ( "name" -> "Yes")));
You need to declare ("name" -> "Yes") as a MongoDBObject because look at:
scala> $pull( "answers" -> ( "name" -> "Yes"))
res10: com.mongodb.casbah.query.Imports.DBObject = { "$pull" : { "answers" : [ "name" , "Yes"]}}
Which is not what you want, you want to pull a subdocument:
scala> $pull ( "answers" -> MongoDBObject("name" -> "Yes") )
res11: com.mongodb.casbah.query.Imports.DBObject = { "$pull" : { "answers" : { "name" : "Yes"}}}

Datatype changes from Int to Float when using group aggregation

I'm trying to use the group aggregation.
I have documents of the following structure in my mongodb:
{ "_id" : ObjectId("4ddcdc9ab4d8a3a90345508e"), "vehicleId" : "1", "timestamp" : ISODate("2011-05-25T10:40:25.856Z"), "speed" : 1 }
{ "_id" : ObjectId("4ddcdc9ab4d8a3a90345508f"), "vehicleId" : "2", "timestamp" : ISODate("2011-05-25T10:40:26.232Z"), "speed" : 2 }
In a test, I want to get the latest speed per vehicleId, i.e. I'm
doing the following:
val key = MongoDBObject("vehicleId" -> true)
val cond = MongoDBObject.empty
val initial = MongoDBObject("timestamp" -> 0)
val reduce =
"""function(doc, prev) {
if (doc.timestamp > prev.timestamp) {
prev.speed = doc.speed;
prev.timestamp = doc.timestamp;
}
}"""
val groupedSpeed = collection.group(key, cond, initial, reduce)
for (dbObject: DBObject <- groupedSpeed) {
println(dbObject.toString)
The weird thing is that in the collection groupedSpeed, the field
speed is not an Int anymore:
{ "vehicleId" : "2" , "timestamp" : { "$date" : "2011-05-25T10:40:49Z"} , "speed" : 2.0}
{ "vehicleId" : "1" , "timestamp" : { "$date" : "2011-05-25T10:40:49Z"} , "speed" : 1.0}
Did I miss something? I'm using casbah 2.1.2.
Cheers,
Christian
[UPDATE] Looks like this is normal in javascript and bson, see here: casbah mailing list
Javascript represents all numeric values as doubles.
You might want to extend your application logic so whenever a new document is inserted, in a separate collection you also update the max speed for that vehicle, so you never have to do group queries.