2

ReactiveMongo collection type provides method findAndRemove which can used to delete one document from a collection based on criteria in query. It returns a Future describing result of delete operation. Calling flatMap() on Future of this Future results in a rather cryptic error message:

type mismatch;
 found   : reactivemongo.api.collections.bson.BSONCollection => scala.concurrent.Future[x$5.BatchCommands.FindAndModifyCommand.FindAndModifyResult] forSome { val x$5: reactivemongo.api.collections.bson.BSONCollection }
 required: reactivemongo.api.collections.bson.BSONCollection => scala.concurrent.Future[S]

I guess this is a result type of an inner class which I cannot use directly. I am unable to understand what should I be doing here to use it. The whole listing is as:

import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.api.{Cursor, DB, MongoConnection, MongoDriver}
import reactivemongo.bson.{BSONDocument, BSONDocumentReader, BSONDocumentWriter, Macros}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._

object ReactiveMongoTest extends App {

  case class Element(symbol: String, atomicNumber: Long, atomicMass: Double)
  implicit val elementReader: BSONDocumentReader[Element] = Macros.reader[Element]
  implicit val elementWriter: BSONDocumentWriter[Element] = Macros.writer[Element]

  val elements = Seq(
    Element("Fe", 26, 55.845),
    Element("Co", 27, 58.933),
    Element("Ni", 28, 58.693)
  )

  def await[T](future: => Future[T]): T = Await.result(future, Duration.Inf)


  lazy val driver: MongoDriver = MongoDriver()
  lazy val conn: MongoConnection = driver.connection(Seq("localhost"))
  def testDb: Future[DB] = conn.database("testDb")
  def testColl: Future[BSONCollection] = testDb.map(_.collection("testColl"))

  def insert = testColl.flatMap(_.insert(ordered = true).many(elements))

  def list = testColl.flatMap {
    _.find(BSONDocument(), projection = Option.empty)
      .cursor[Element]()
      .collect[Seq](Int.MaxValue, Cursor.FailOnError[Seq[Element]]())
  }

  def remove = testColl.flatMap(_.findAndRemove(BSONDocument("atomicNumber" -> 26)))

  println(await(insert))

  await(list).foreach(x => println(s"listing -> ${x}"))

//  println(await(remove))

  println("After removing!")
  await(list).foreach(x => println(s"listing -> ${x}"))

  sys.exit()

}

Error message is:

Error:(37, 48) type mismatch;
 found   : reactivemongo.api.collections.bson.BSONCollection => scala.concurrent.Future[x$4.BatchCommands.FindAndModifyCommand.FindAndModifyResult] forSome { val x$4: reactivemongo.api.collections.bson.BSONCollection }
 required: reactivemongo.api.collections.bson.BSONCollection => scala.concurrent.Future[S]
  def remove = testColl.flatMap(_.findAndRemove(BSONDocument("atomicNumber" -> 26)))

Update 1: Calling map works:

def remove = testColl.map(_.findAndRemove(BSONDocument("atomicNumber" -> 26)))
println(await(await(remove)))
Xolve
  • 22,298
  • 21
  • 77
  • 125

1 Answers1

4

ReactiveMongo is way too clever with a lot of these inner case classes, etc., and every time I've had to use it I've run into weird problems like this. You can force it to compile by providing a type annotation that fixes the scope of the existential:

scala> type FAMResult =
     |   c.BatchCommands.FindAndModifyCommand.FindAndModifyResult forSome {
     |     val c: BSONCollection
     |   }
defined type alias FAMResult

scala> def remove =
     |   testColl.flatMap[FAMResult](_.findAndRemove(BSONDocument("atomicNumber" -> 26)))
remove: scala.concurrent.Future[FAMResult]

Unless you don't care about the removal command results, though, this probably isn't ideal. A better approach is to map into the Future[<blah blah blah>.FindAndModifyResult] inside the flatMap so that you end up with a more useful type:

scala> def remove = testColl.flatMap(
     |   _.findAndRemove(BSONDocument("atomicNumber" -> 26)).map(_.value)
     | )
remove: scala.concurrent.Future[Option[reactivemongo.bson.BSONDocument]]

You could also just .map(_ => ()) if you actually don't care about the result, .map(_.result[Element]) if you want the result decoded, etc.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • This is exactly what I was looking for! And now I am reading about existential. Thank you for providing a simple enough answer to read and apply. – Xolve Feb 12 '19 at 18:26
  • For a case like above, it is important to check the response of database operation. I wonder why it is an inner class for a definitely public API. – Xolve Feb 12 '19 at 18:28
  • @Xolve Do you mean your `map` (instead of `flatMap`) or the `map` in my answer (inside the `flatMap`)? – Travis Brown Feb 12 '19 at 19:26
  • I mean "my `map` (instead of `flatMap`)" :-) If a type is not available to flatMap why it would be available to map? I think its related to how as a workaround you suggested to use different type so the inner class type doesn't come out. But I do call `await` twice and can use the object (in `println`), (on the other hand I don't use the type). – Xolve Feb 12 '19 at 19:37
  • @Xolve The problem has to do with unifying `Future[] forSome { }` and `Future[S]` during type inference in the `flatMap`. With the `map` nothing like this is necessary—there's just a future inside of a future, and you can call `Await.result` with a `Future[...] forSome { ... }` just fine. – Travis Brown Feb 12 '19 at 21:59