Reactive Scala Driver for MongoDB

Asynchronous & Non-Blocking

ReactiveMongo 1.0.0-rc.1-SNAPSHOT – Release details

This is a Release Candidate

What’s new?

The documentation is available online, and its code samples are compiled to make sure it’s up-to-date.

Compatibility

This release is compatible with the following runtime.

MongoDB versions older than 3.0 are not longer (end of life 2018-2).

Recommended configuration:

The driver core and the modules are tested in a container based environment, with the specifications as bellow.

This can be considered as a recommended environment.

Migration

A Scalafix module is available to migrate from ReactiveMongo 0.12+ to 1.0 (not yet available for Scala 2.13).

To apply the migration rules, first setup Scalafix in the SBT build, then configure the ReactiveMongo rules as bellow.

scalafixDependencies in ThisBuild ++= Seq(
  "org.reactivemongo" %% "reactivemongo-scalafix" % "1.0.0-rc.1-SNAPSHOT")

Once the rules are configured, they can be applied from SBT.

scalafix ReactiveMongoUpgrade
scalafix ReactiveMongoLinter --check

Then upgrade the appropriate libraryDependencies in the SBT build.

Connection

The MongoDriver type is replaced by AsyncDriver, with asynchronous methods.

The utility function MongoConnection.parseURI is replaced by asynchronous function .fromString.

Also, the following options are deprecated:

New options:

SNI is now supported for the SSL connection.

The x.509 certificate authentication is now supported, and can be configured by setting x509 as authenticationMechanism, and with the following new options.

import reactivemongo.api._

def connection(driver: AsyncDriver) =
  driver.connect("mongodb://server:27017/db?ssl=true&authenticationMechanism=x509&keyStore=file:///path/to/keystore.p12&keyStoreType=PKCS12")

The DNS seedlist is now supported, using mongodb+srv:// scheme in the connection URI. It’s also possible to specify the credential directly in the URI.

import reactivemongo.api._

def seedListCon(driver: AsyncDriver) =
  driver.connect("mongodb+srv://usr:pass@mymongo.mydomain.tld/mydb")

The option rm.monitorRefreshMS is renamed heartbeatFrequencyMS.

See documentation

Netty native

The internal Netty dependency has been updated to the version 4.1.

It comes with various improvements (memory consumption, …), and also to use Netty native support (kqueue for Mac OS X and epoll for Linux, on x86_64 arch).

Note that the Netty dependency is shaded so it won’t conflict with any Netty version in your environment.

See documentation

BSON library

The Biːsən is the new default BSON library, that fixes some issues (OOM), and brings multiple API and performance improvements (simpler & better).

Highlights:

Documents and values

The API for BSONDocument has been slightly updated, with the function getAs renamed as getAsOpt (to be consistent with getAsTry).

New field utilities are provided for the most common types:

import reactivemongo.api.bson._

def foo(doc: BSONDocument): Unit = {
  val i: Option[Int] = doc.int("fieldNameInt")
  val d: Option[Double] = doc.double("fieldNameDouble")
  val l: Option[Long] = doc.long("fieldNameLong")
  val s: Option[String] = doc.string("fieldNameStr")
  val a: Option[Seq[BSONValue]] = doc.array("fieldNameArr")
  val b: Option[Boolean] = doc.booleanLike("fieldNameBool")
  val c: Option[BSONDocument] = doc.child("nestedDoc")
  val _: List[BSONDocument] = doc.children("arrayOfDocs")
}

Note: The BSONDocument and BSONArray factories have been optimized and support more use cases.

Document benchmarks
Coefficient between new/old throughput (op/s; =1: no change, 1+: better thrpt). Source: BSONDocumentBenchmark, BSONDocumentHandlerBenchmark
Array benchmarks
Coefficient between new/old throughput (op/s; =1: no change, 1+: better thrpt). Source: BSONArrayBenchmark

The Biːsən library supports BSON Decimal128 (MongoDB 3.4+).

See documentation

Reader and writer typeclasses

The names of these typeclasses are unchanged (BSONReader and BSONWriter), except the package that is reactivemongo.api.bson (instead of reactivemongo.bson).

In the previous BSON library, BSONReader and BSONWriter are defined with two type parameters:

BSONReader[B <: BSONValue, T]

BSONWriter[T, B <: BSONValue]

The new API has been simplified, with only the T type parameter kept.

import reactivemongo.api.bson._

// read a String from BSON, whatever is the specific BSON value type
def stringReader: BSONReader[String] = ???

Not only it makes the API simpler, but it also allows to read different BSON types as a target Scala type (before only supported for numeric/boolean, using the dedicated typeclasses). For example, the Scala numeric types (BigDecimal, Double, Float, Int, Long) can be directly read from any consistent BSON numeric type (e.g. 1.0 as integer 1), without having to use BSONNumberLike.

Also, handler functions readTry and writeTry returns Try, for a safer representation of possible failures.

The new API is also safer, replacing BSONReader.read and BSONWriter.write respectively with BSONReader.readTry and BSONWriter.writeTry, so that serialization errors can be handle at typelevel. In a similar way, BSONObjectID.parse now returns Try.

Like the current BSON library, some specific typeclasses are available (with same names) to only work with BSON documents: BSONDocumentReader and BSONDocumentWriter.

Some new handlers are provided by default, like those for Java Time types.

Note: The handler for java.util.Date is replaced the handler for java.time.Instant.

The error handling has also been improved, with more details (Note: DocumentKeyNotFoundException is the previous API is replaced with BSONValueNotFoundException in the new one).

Reader benchmarks
Coefficient between new/old throughput (op/s; =1: no change, 1+: better thrpt). Source: BSON reader benchmarks

Map handler:

A handler is now available to write and read Scala Map as BSON, provided the value types are supported.

import scala.util.Try
import reactivemongo.api.bson._

def bsonMap = {
  val input: Map[String, Int] = Map("a" -> 1, "b" -> 2)

  // Ok as key and value (String, Int) are provided BSON handlers
  val doc: Try[BSONDocument] = BSON.writeDocument(input)

  val output = doc.flatMap { BSON.readDocument[Map[String, Int]](_) }
}

For cases where you can to serialize a Map whose key type is not String (which is required for BSON document keys), new typeclasses KeyWriter and KeyReader have been introduced.

import scala.util.Try
import reactivemongo.api.bson._

final class FooKey(val value: String)

object FooKey {
  val bar = new FooKey("bar")
  val lorem = new FooKey("lorem")

  implicit val keyWriter: KeyWriter[FooKey] = KeyWriter.safe[FooKey](_.value)

  implicit val keyReader: KeyReader[FooKey] =
    KeyReader[FooKey] { new FooKey(_) }

}

def bsonMapCustomKey = {
  val input: Map[FooKey, Int] = Map(
    FooKey.bar -> 1, FooKey.lorem -> 2)

  // Ok as key and value (String, Int) are provided BSON handlers
  val doc: Try[BSONDocument] = BSON.writeDocument(input)

  val output = doc.flatMap { BSON.readDocument[Map[FooKey, Int]](_) }
}

Macros

The new library also provide similar macros, to easily materialized document readers and writers for Scala case classes and sealed traits.

case class Person(name: String, age: Int)

import reactivemongo.api.bson._

val personHandler: BSONDocumentHandler[Person] = Macros.handler[Person]

// Or only ...
val personReader: BSONDocumentReader[Person] = Macros.reader[Person]
val personWriter: BSONDocumentWriter[Person] = Macros.writer[Person]

This macro utilities offer new configuration mechanism.

The macro configuration can be used to specify a field naming, to customize the name of each BSON field corresponding to Scala field.

import reactivemongo.api.bson._

val withPascalCase: BSONDocumentHandler[Person] = {
  implicit def cfg: MacroConfiguration = MacroConfiguration(
    fieldNaming = FieldNaming.PascalCase)

  Macros.handler[Person]
}

withPascalCase.writeTry(Person(name = "Jane", age = 32))
/* Success {
  BSONDocument("Name" -> "Jane", "Age" -> 32)
} */

In a similar way, when using macros with sealed family/trait, the strategy to name the discriminator field and to set a Scala type as discriminator value can be configured.

import reactivemongo.api.bson._

sealed trait Family1
case class Foo1(bar: String) extends Family1
case class Lorem1(ipsum: Int) extends Family1

implicit val foo1Handler = Macros.handler[Foo1]
implicit val lorem1Handler = Macros.handler[Lorem1]

val family1Handler: BSONDocumentHandler[Family1] = {
  implicit val cfg: MacroConfiguration = MacroConfiguration(
    discriminator = "_type",
    typeNaming = TypeNaming.SimpleName.andThen(_.toLowerCase))

  Macros.handler[Family1]
}

The nested type Macros.Options is replaced by similar type MacrosOptions.

Note: The Macros.Options.SaveSimpleName of the previous BSON library has been removed in favour of a configuration factory using similar TypeNaming.

The compile-time option AutomaticMaterialization has been added, when using the macros with sealed family, to explicitly indicate when you want to automatically materialize required instances for the subtypes (if missing from the implicit scope).

sealed trait Color

case object Red extends Color
case object Blue extends Color
case class Green(brightness: Int) extends Color
case class CustomColor(code: String) extends Color

object Color {
  import reactivemongo.api.bson.{ Macros, MacroOptions },
    MacroOptions.{ AutomaticMaterialization, UnionType, \/ }

  // Use `UnionType` to define a subset of the `Color` type,
  type PredefinedColor =
    UnionType[Red.type \/ Green \/ Blue.type] with AutomaticMaterialization

  val predefinedColor = Macros.handlerOpts[Color, PredefinedColor]
}

Note: A new option MacroOptions.DisableWarnings allows to specifically exclude macro warnings.

Using the new option MacroOptions.ReadDefaultValues, the default values can be used by BSON reader when there is no corresponding BSON value.

import reactivemongo.api.bson.{
  BSONDocument, BSONDocumentReader, Macros, MacroOptions
}

case class FooWithDefault1(id: Int, title: String = "default")

{
  val reader: BSONDocumentReader[FooWithDefault1] =
    Macros.using[MacroOptions.ReadDefaultValues].reader[FooWithDefault1]

  reader.readTry(BSONDocument("id" -> 1)) // missing BSON title
  // => Success: FooWithDefault1(id = 1, title = "default")
}

Annotations:

The way Option is handled by the macros has been improved, also with a new annotation @NoneAsNull, which write None values as BSONNull (instead of omitting field/value).

A new annotation @Flatten has been added, to indicate to the macros that the representation of a property must be flatten rather than a nested document.

import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.Macros.Annotations.Flatten

case class Range(start: Int, end: Int)

case class LabelledRange(
  name: String,
  @Flatten range: Range)

// Flattened with macro as bellow:
BSONDocument("name" -> "foo", "start" -> 0, "end" -> 1)

// Rather than:
// BSONDocument("name" -> "foo", "range" -> BSONDocument(
//   "start" -> 0, "end" -> 1))

The new @DefaultValue can be used with MacroOptions.ReadDefaultValues to specify a default value only used when reading from BSON.

import reactivemongo.api.bson.{
  BSONDocument, BSONDocumentReader, Macros, MacroOptions
}
import Macros.Annotations.DefaultValue

case class FooWithDefault2(
  id: Int,
  @DefaultValue("default") title: String)

{
  val reader: BSONDocumentReader[FooWithDefault2] =
    Macros.using[MacroOptions.ReadDefaultValues].reader[FooWithDefault2]

  reader.readTry(BSONDocument("id" -> 1)) // missing BSON title
  // => Success: FooWithDefault2(id = 1, title = "default")
}

The new @Reader allows to indicate a specific BSON reader that must be used for a property, instead of resolving such reader from the implicit scope.

import reactivemongo.api.bson.{
  BSONDocument, BSONDouble, BSONString, BSONReader
}
import reactivemongo.api.bson.Macros,
  Macros.Annotations.Reader

val scoreReader: BSONReader[Double] = BSONReader.collect[Double] {
  case BSONString(v) => v.toDouble
  case BSONDouble(b) => b
}

case class CustomFoo1(
  title: String,
  @Reader(scoreReader) score: Double)

val reader = Macros.reader[CustomFoo1]

reader.readTry(BSONDocument(
  "title" -> "Bar",
  "score" -> "1.23" // accepted by annotated scoreReader
))
// Success: CustomFoo1(title = "Bar", score = 1.23D)

In a similar way, the new @Writer allows to indicate a specific BSON writer that must be used for a property, instead of resolving such writer from the implicit scope.

import reactivemongo.api.bson.{ BSONString, BSONWriter }
import reactivemongo.api.bson.Macros,
  Macros.Annotations.Writer

val scoreWriter: BSONWriter[Double] = BSONWriter[Double] { d =>
  BSONString(d.toString) // write double as string
}

case class CustomFoo2(
  title: String,
  @Writer(scoreWriter) score: Double)

val writer = Macros.writer[CustomFoo2]

writer.writeTry(CustomFoo2(title = "Bar", score = 1.23D))
// Success: BSONDocument("title" -> "Bar", "score" -> "1.23")

Extra libraries

Some extra libraries are provided along the new BSON one, to improve the integration.

GeoJSON:

A new GeoJSON library is provided, with the geometry types and the corresponding handlers to read from and write them to appropriate BSON representation.

It can be configured in the build.sbt as below.

libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-geo" % "1.0.0-rc.1-SNAPSHOT"

See Scaladoc

Monocle:

The library that provides Monocle based optics, for BSON values.

It can be configured in the build.sbt as below.

libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-monocle" % "1.0.0-rc.1-SNAPSHOT"

See Scaladoc

Specs2:

The Specs2 library provides utilities to write tests using specs2 with BSON values.

It can be configured in the build.sbt as below.

libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-specs2" % "1.0.0-rc.1-SNAPSHOT"

See Scaladoc

Query and write operations

The query builder supports more options (see find).

The collection API provides new builders for write operations. This supports bulk operations (e.g. insert many documents at once).

InsertBuilder

The new insert operation is providing an InsertBuilder, which supports,

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.commands.WriteResult

import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

val document1 = BSONDocument(
  "firstName" -> "Stephane",
  "lastName" -> "Godbillon",
  "age" -> 29)

// Simple: .insert.one(t)
def simpleInsert(coll: BSONCollection): Future[WriteResult] =
  coll.insert.one(document1)

// Bulk: .insert.many(Seq(t1, t2, ..., tN))
def bulkInsert(coll: BSONCollection): Future[coll.MultiBulkWriteResult] =
  coll.insert(ordered = false).many(Seq(
    document1, BSONDocument(
      "firstName" -> "Foo",
      "lastName" -> "Bar",
      "age" -> 1)))

UpdateBuilder

The new update operation returns an UpdateBuilder, which can be used to perform simple or bulk update.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

def update1(personColl: BSONCollection) = {
  val selector = BSONDocument("name" -> "Jack")

  val modifier = BSONDocument(
    "$set" -> BSONDocument(
      "lastName" -> "London",
      "firstName" -> "Jack"),
      "$unset" -> BSONDocument("name" -> 1))

  // Simple update: get a future update
  val futureUpdate1 = personColl.update.one(
    q = selector, u = modifier,
    upsert = false, multi = false)

  // Bulk update: multiple update
  val updateBuilder1 = personColl.update(ordered = true)
  val updates = Future.sequence(Seq(
    updateBuilder1.element(
      q = BSONDocument("firstName" -> "Jane", "lastName" -> "Doh"),
      u = BSONDocument("age" -> 18),
      upsert = true,
      multi = false),
    updateBuilder1.element(
      q = BSONDocument("firstName" -> "Bob"),
      u = BSONDocument("age" -> 19),
      upsert = false,
      multi = true)))

  val bulkUpdateRes1 = updates.flatMap { ops => updateBuilder1.many(ops) }
}

DeleteBuilder

The .delete function returns a DeleteBuilder, to perform simple or bulk delete.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

def simpleDelete1(personColl: BSONCollection) =
  personColl.delete.one(BSONDocument("firstName" -> "Stephane"))

def bulkDelete1(personColl: BSONCollection) = {
  val deleteBuilder = personColl.delete(ordered = false)

  val deletes = Future.sequence(Seq(
    deleteBuilder.element(
      q = BSONDocument("firstName" -> "Stephane"),
      limit = Some(1), // former option firstMatch
      collation = None),
    deleteBuilder.element(
      q = BSONDocument("lastName" -> "Doh"),
      limit = None, // delete all the matching document
      collation = None)))

  deletes.flatMap { ops => deleteBuilder.many(ops) }
}

The .remove operation is now deprecated.

arrayFilters

The arrayFilters criteria is supported for findAndModify and update operations.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.WriteConcern
import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

def findAndUpdateArrayFilters(personColl: BSONCollection) =
  personColl.findAndModify(
    selector = BSONDocument.empty,
    modifier = personColl.updateModifier(
      update = BSONDocument(f"$$set" -> BSONDocument(
        f"grades.$$[element]" -> 100)),
      fetchNewObject = true,
      upsert = false),
    sort = None,
    fields = None,
    bypassDocumentValidation = false,
    writeConcern = WriteConcern.Journaled,
    maxTime = None,
    collation = None,
    arrayFilters = Seq(
      BSONDocument("elem.grade" -> BSONDocument(f"$$gte" -> 85))))

def updateArrayFilters(personColl: BSONCollection) =
  personColl.update.one(
    q = BSONDocument("grades" -> BSONDocument(f"$$gte" -> 100)),
    u = BSONDocument(f"$$set" -> BSONDocument(
      f"grades.$$[element]" -> 100)),
    upsert = false,
    multi = true,
    collation = None,
    arrayFilters = Seq(
      BSONDocument("element" -> BSONDocument(f"$$gte" -> 100))))

The .count(..) collection operation now return a Long value (rather than Int).

More: Find documents, Write documents

Play

Play integration has been upgraded, to support new versions and be compatible with the new BSON library.

The JSONCollection and JSONSerializationPack (from package reactivemongo.play.json.collection) have been removed, and JSON compatibility can be applied using standard collection and JSON conversions.

import reactivemongo.play.json.compat._,
  json2bson._,
  bson2json._

The play.modules.reactivemongo.JSONFileToSave has also been removed.

Aggregation

There are newly supported by the Aggregation Framework.

addFields:

The $addFields stage can now be used.

import scala.concurrent.ExecutionContext

import reactivemongo.api.bson.collection.BSONCollection

def sumHomeworkQuizz(students: BSONCollection) =
  students.aggregateWith1[BSONDocument]() { framework =>
    import framework.AddFields

    AddFields(document(
      "totalHomework" -> document(f"$$sum" -> f"$$homework"),
      "totalQuiz" -> document(f"$$sum" -> f"$$quiz"))) -> List(
      AddFields(document(
        "totalScore" -> document(f"$$add" -> array(
        f"$$totalHomework", f"$$totalQuiz", f"$$extraCredit")))))
  }

bucketAuto:

The $bucketAuto stage introduced by MongoDB 3.4 can be used as bellow.

import scala.concurrent.ExecutionContext

import reactivemongo.api.Cursor

import reactivemongo.api.bson._
import reactivemongo.api.bson.collection.BSONCollection

def populationBuckets(zipcodes: BSONCollection)(implicit ec: ExecutionContext) =
  zipcodes.aggregateWith[BSONDocument]() { framework =>
    import framework.BucketAuto

    BucketAuto(BSONString(f"$$population"), 2, None)() -> List.empty
  }.collect[Set](Int.MaxValue, Cursor.FailOnError[Set[BSONDocument]]())

count:

If the goal is only to count the aggregated documents, the $count stage can be used.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.Cursor

import reactivemongo.api.bson.{ BSONDocument, BSONDocumentReader, BSONString }
import reactivemongo.api.bson.collection.BSONCollection

def countPopulatedStates1(col: BSONCollection): Future[Int] = {
  implicit val countReader = BSONDocumentReader[Int] { doc =>
    doc.getAsTry[Int]("popCount").get
  }

  col.aggregateWith[Int]() { framework =>
    import framework.{ Count, Group, Match, SumField }

    Group(BSONString("$state"))(
      "totalPop" -> SumField("population")) -> List(
        Match(BSONDocument("totalPop" -> BSONDocument("$gte" -> 10000000L))),
        Count("popCount"))
  }.head
}

facet:

The $facet stage is now supported, to create multi-faceted aggregations which characterize data across multiple dimensions, or facets.

import reactivemongo.api.bson.collection.BSONCollection

def useFacetAgg(coll: BSONCollection) = {
  import coll.AggregationFramework.{ Count, Facet, Out, UnwindField }

  Facet(Seq(
    "foo" -> (UnwindField("bar"), List(Count("c"))),
    "lorem" -> (Out("ipsum"), List.empty)))
  /* {
    $facet: {
      'foo': [
        { '$unwind': '$bar' },
        { '$count': 'c' }
      ],
      'lorem': [
        { '$out': 'ipsum' }
      ]
    }
  } */
}

filter:

The $filter stage is now supported.

import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.Cursor

import reactivemongo.api.bson.{ BSONArray, BSONDocument, BSONString }
import reactivemongo.api.bson.collection.BSONCollection

def salesWithItemGreaterThanHundered(sales: BSONCollection) =
  sales.aggregateWith[BSONDocument]() { framework =>
    import framework._

    val sort = Sort(Ascending("_id"))

    Project(BSONDocument("items" -> Filter(
      input = BSONString(f"$$items"),
      as = "item",
      cond = BSONDocument(
        f"$$gte" -> BSONArray(f"$$$$item.price", 100))))) -> List(sort)

  }.collect[List](Int.MaxValue, Cursor.FailOnError[List[BSONDocument]]())

replaceRoot:

The $replaceRoot stage is now supported.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

/* For a fruits collection:
{
   "_id" : 1,
   "fruit" : [ "apples", "oranges" ],
   "in_stock" : { "oranges" : 20, "apples" : 60 },
   "on_order" : { "oranges" : 35, "apples" : 75 }
}, ...
*/

def replaceRootTest(fruits: BSONCollection): Future[Option[BSONDocument]] = {
  fruits.aggregateWith[BSONDocument]() { framework =>
    import framework._

    ReplaceRootField("in_stock") -> List.empty
  }.headOption
  // Results: { "oranges": 20, "apples": 60 }, ...
}

search:

In ReactiveMongo the Atlas Search feature can be applied through the aggregation framework.

import scala.concurrent.{ ExecutionContext, Future }

import reactivemongo.api.Cursor
import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

def foo(col: BSONCollection)(
  implicit ec: ExecutionContext): Future[List[BSONDocument]] = {

  import col.AggregationFramework.AtlasSearch, AtlasSearch.Term

  col.aggregatorContext[BSONDocument](AtlasSearch(Term(
    path = "description",
    query = "s*l*",
    modifier = Some(Term.Wildcard) // wildcard: true
  ))).prepared.cursor.
    collect[List](-1, Cursor.FailOnError[List[BSONDocument]]())

}

slice:

The $slice operator is also supported as bellow.

import scala.concurrent.ExecutionContext

import reactivemongo.api.Cursor

import reactivemongo.api.bson._
import reactivemongo.api.bson.collection.BSONCollection

def sliceFavorites(coll: BSONCollection)(implicit ec: ExecutionContext) =
  coll.aggregateWith[BSONDocument]() { framework =>
    import framework.{ Project, Slice }

    Project(BSONDocument(
      "name" -> 1,
      "favorites" -> Slice(
        array = BSONString(f"$$favorites"),
        n = BSONInteger(3)))) -> List.empty
  }.collect[Seq](4, Cursor.FailOnError[Seq[BSONDocument]]())

Miscellaneous: Other stages are also supported.

Change stream:

Since MongoDB 3.6, it’s possible to watch the changes applied on a collection.

Now ReactiveMongo can obtain a stream of changes, and aggregate it.

import reactivemongo.api.Cursor
import reactivemongo.api.bson.BSONDocument
import reactivemongo.api.bson.collection.BSONCollection

def filteredWatch(
  coll: BSONCollection,
  filter: BSONDocument): Cursor[BSONDocument] = {
  import coll.AggregationFramework.{ Match, PipelineOperator }

  coll.watch[BSONDocument](
    pipeline = List[PipelineOperator](Match(filter))).
    cursor[Cursor.WithOps]
}

More: Aggregation Framework

GridFS

The GridFS API has been refactored, to be simpler and more safe.

The DefaultFileToSave has been moved to the factory fileToSave.

Separate classes and traits DefaultReadFile, ComputedMetadata, BasicMetadata and CustomMetadata have been merged with the single class ReadFile.

Monitoring

A new module is available to collect ReactiveMongo metrics with Kamon.

"org.reactivemongo" %% "reactivemongo-kamon" % "1.0.0-rc.1-SNAPSHOT"

Then the metrics can be configured in dashboards, according the used Kamon reporters. For example if using Kamon APM.

Graph about established connections

More: Monitoring

Administration

The operations to manage a MongoDB instance can be executed using ReactiveMongo. This new release has new functions for DB administration.

Ping:

The DB has now a ping operation, to execute a ping command.

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import reactivemongo.api.DB

def ping(admin: DB): Future[Boolean] = admin.ping()

Breaking changes

The Typesafe Migration Manager has been setup on the ReactiveMongo repository. It will validate all the future contributions, and help to make the API more stable.

For the current 1.0.0-rc.1-SNAPSHOT release, it has detected the following breaking changes.

Test coverage

Connection

Operations and commands

Core/internal

Suggest changes