Search code examples
mongodbscalacasbah

Scala casbah DSL queries


I'm learning Scala and trying Mongo too. I'm creating a function that receives a Map[String, Any] as a parameter and I would like to return the proper MongoDBObject for it:

def parse(query: Map[String, Any]): MongoDBObject = {
  val result = query("operation") match {
    case "all" => query("field").toString $all query("value").asInstanceOf[List[String]]
    case "in" => query("field").toString $in query("value").asInstanceOf[List[String]]
    case "regex" => query("field").toString $regex query("value")
    case "eq" => query("field").toString $eq query("value")
    case "gt" => query("field").toString $gt query("value")
    case "gte" => query("field").toString $gte query("value")
    case "lt" => query("field").toString $lt query("value")
    case "lte" => query("field").toString $lte query("value")
    case "exists" => query("field").toString $exists query("value").asInstanceOf[Boolean]
    case "size" => query("field").toString $size query("value").asInstanceOf[Int]
    case "where" => $where(query("value").toString)
    case _ => throw new NotImplementedError("Unknown operation")
  }
}

I have some issues.

  • the compiler says $regex is not a member of String. I don't know why.
  • the compiler says that Any is not a valid query parameter. I suppose I should cast to int, string, date or any other valid Mongo type. Is there any way to fix this without reflection to solve wich type the value is?
  • for the $mod operation I should give two numeric values as parameteres. Should I use a List as value for the map and get first and second items?

Solution

  • It complains about $regex because it isn't finding a regex-able object on the right hand side to apply the conversion used to parse the $regex method--this is a problem you will run into with all of the following calls as well.

    For the issue with Any (and also $mod), may I suggest a different approach? You have no type information with Any, so you can't really get around runtime casting (and I'm not sure how reflection would help you either.) You're not getting any of the benefits of a static type system that way. Here's a sketch of one way you could implement a method like this using a type hierarchy to enforce type safety:

    sealed trait Query
    
    case class All(field: String, value: List[String]) extends Query 
    ...
    case class GreaterThan(field: String, value: Int) extends Query 
    ...
    case class Mod(field: String, divisor: Int, remainder: Int) extends Query
    
    def parse(q: Query): MongoDBObject = {
       q match {
          case All(f, v) => f $all v
          ...
          case GreaterThan(f, v) => f $gt v
          ...
          case Mod(f, d, r) => f $mod (d, r)
       }
    }
    

    Alternately, you could define an abstract execute method on Query and override it in each extending class instead of doing the match statement in parse. From there, you could abstract further using type parameters or generic types to allow, for example, GreaterThan to take any Numeric type.