0

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?
John Dvorak
  • 26,799
  • 13
  • 69
  • 83
Augusto
  • 1,234
  • 1
  • 16
  • 35

1 Answers1

1

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.

  • Much better aproach. But still I will have to cast my Any to a type. I'm using this Map[String,Any] because I'm parsing a JSON using the scala native parsers. And it gives me a Map[String, Any], so I will have to cast this Any to the proper type. I thought about reflection to check wich type it is and return it with the proper cast. About the $regex issue, I've used the tutorial in the docs that points me to use SBT, so I think it downloaded the latest jar. I've navigated through the jar and I've found the $regex function there... I have no idea why the compiler is complaining about this – Augusto Sep 18 '13 at 12:01
  • 1
    If you have control over the JSON message format, you can use json4s (https://github.com/json4s/json4s) to both serialize & deserialize case classes easily. Even if you don't, using that library you could extract the query type and then deserialize accordingly. – Kelsey Gilmore-Innis Sep 18 '13 at 17:55
  • Hi again... I'm trying the examples from their [website](https://github.com/json4s/json4s#extracting-values) and I'm always getting an error (Malformed class name) – Augusto Sep 18 '13 at 23:46
  • I did it with Lift-json. But I still have a doubt In a case like $eq that supports multiple Types, how can I solve the problem? I can't have two classes with the same name. So I can't have a Eq(field:String, value: Int) and a Eq(field:string, value:String) – Augusto Sep 19 '13 at 00:16
  • The classes could be named anything you want. If you're feeling especially advanced, you might look at how the casbah library writers handled this issue, since it's open source: https://github.com/mongodb/casbah/blob/master/casbah-query/src/main/scala/CoreOperators.scala#L142 https://github.com/mongodb/casbah/blob/master/casbah-query/src/main/scala/Implicits.scala#L182 – Kelsey Gilmore-Innis Sep 19 '13 at 02:04