I am attempting to provide an API to search a MongoDB collection on various criteria, including a full-text search. Since this is a Scala project (in Play FWIW), I am using Salat, an abstraction around Casbah.
The following code works fine:
MySalatDao
.find(MongoDBObject("$text" -> MongoDBObject("$search" -> "Vidya")), MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")))
.sort(orderBy = MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")))
However, I will eventually need to search on multiple criteria and sort the results by their full-text search score, so I explored Casbah's MongoDBObject query builder functionality (at bottom).
So I tried to replicate the above like this:
val builder = MongoDBObject.newBuilder
builder += "$text" -> MongoDBObject("$search" -> "Vidya")
builder += "score" -> MongoDBObject("$meta" -> "textScore")
MySalatDao
.find(a.result())
.sort(orderBy = MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")))
This gives the following exception:
com.mongodb.MongoException: Can't canonicalize query: BadValue must have $meta projection for all $meta sort keys
at com.mongodb.QueryResultIterator.throwOnQueryFailure(QueryResultIterator.java:214)
at com.mongodb.QueryResultIterator.init(QueryResultIterator.java:198)
at com.mongodb.QueryResultIterator.initFromQueryResponse(QueryResultIterator.java:176)
at com.mongodb.QueryResultIterator.<init>(QueryResultIterator.java:64)
at com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:86)
at com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:66)
.
.
I've seen this error before--when I didn't include the score
component in the query. But once I did that, it worked (as seen in the first code snippet), and I thought the version with the query builder is equivalent.
For that matter, a call to builder.result().toString()
produces this:
{ "$text" : { "$search" : "Vidya"} , "score" : { "$meta" : "textScore"}}
Any help with getting the query builder to work for me would be much appreciated.
In your working query you are passing one DBObject for the "query predicate" and a second DBObject for "fields" or "projection" - find takes a second optional argument indicating which fields to return and in case of $text search, there is a special projection field $meta which allows you to get back the score of the matched document so that you can sort on it.
In your builder attempt you are adding the projection DBObject to the query criteria, and that gives you the same error you saw before when omitting the score component as the second argument to find.
Add MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore"))
as the second argument to find like you were doing before, and use builder
for combining multiple query criteria.
In simple JSON terms, you are calling find like this:
db.coll.find( { "$text" : { "$search" : "Vidya"} , "score" : { "$meta" : "textScore"}} )
when you really want to call it like this:
db.coll.find( { "$text" : { "$search" : "Vidya"} } , { "score" : { "$meta" : "textScore"}} )