Skip to content

v2.0.0

Choose a tag to compare

@shadowsmind shadowsmind released this 01 Sep 22:35
· 242 commits to main since this release

mongo4s 2.0.0

Two things drive it. The write API's options moved out of method parameters and into immutable values, which is what
lets every future option ship in a 2.x minor instead of another major. And an independent audit of the codebase
found a set of real defects, all fixed here, each reproduced by a failing test first.

The compiler catches every breaking change — none of them alters behaviour silently. The full migration guide is in
COMPATIBILITY.md.

libraryDependencies += "org.mongo4s" %% "mongo4s-core" % "2.0.0"

New features

Read and write concerns per database and collection. withReadConcern, withWriteConcern and
withReadPreference return a new handle rather than mutating the one you hold, so per-operation control is just
chaining: collection.withWriteConcern(WriteConcern.MAJORITY).insertOne(x). Previously these could only be set for
the whole client, and there was no way to build a MongoCollection from a driver collection you had narrowed
yourself.

mongo4s-testkit, a new published module. FakeMongoCollection used to live in mongo4s-repositories' test
sources, which meant nobody outside this build could use it. It is now published, together with a new
FakeRepository — a real BaseMongoRepository over a fake collection, so the repository logic under test is the one
that ships.

arrayFilters and the positional operators. $[] and $[identifier] are ordinary path segments, so
Field.of[Order, List[Item]](_.items) / "$[low]" / "qty" builds them; the filters themselves go in UpdateOptions.

Write, delete and count options. collation, hint and comment on every write, bypassDocumentValidation
where a whole document is written, and limit/skip/maxTime on count.

$each modifiers on pushAll — $slice, $sort, $position, for a capped, ordered array maintained by the
server rather than by the application.

More index types — hashed, 2dsphere, 2d, plus hidden, collation and wildcardProjection. Wildcard
indexes needed no new API: $** is a stored path.

Richer aggregation — $out and $merge take a target database, and $merge its on/whenMatched/
whenNotMatched policies; $lookup gained its sub-pipeline form with let; $graphLookup is new.

omitNoneFields (default on). A None field is left out of the document instead of written as an explicit
null, which is what a consumer asked for after reporting a 2×+ drop in memory footprint. Reads are unaffected in
both directions, so mixed collections keep working.

BsonTypeName is public and an enum, so hasType takes a value rather than a $type alias string that a typo
turns into a filter matching nothing.

API changes

Every item here is a compile error, not a behaviour change.

Write options are values, not parameters. UpdateOptions, ReplaceOptions, DeleteOptions, CountOptions and
the entity-typed FindOneAndUpdateOptions[A] / FindOneAndReplaceOptions[A] / FindOneAndDeleteOptions[A]:

collection.updateOne(filter, update, upsert = true)         // 1.x
collection.updateOne(filter, update, UpdateOptions.upsert)  // 2.0

deleteOne, deleteMany and count gained an options parameter. WriteCommand and Repository follow.

Projection is two types. Mixing inclusion with exclusion no longer compiles, where it used to silently discard
whatever you listed first and return more fields than you asked for. Projection.empty returns
Projection.Everything[E].

Index and WireCodecConfig are no longer case classes. Both are final classes with private constructors and
withX builders, for the same reason: a case class cannot gain a field without breaking apply/copy again in
every release, and options keep arriving.

hasType takes BsonTypeName, and BsonError.TypeMismatch carries it in both fields. The rendered message is
unchanged.

MongoCollection and MongoDatabase gained three abstract methods — the concern setters — which matters if you
implement either trait outside this library. Stage gained LookupPipeline and GraphLookup cases, which matters
if you match on it exhaustively.

FakeMongoCollection moved to mongo4s.testkit, in the new module.

Bug fixes

Each of these was reproduced by a failing test before being fixed; the codec and query fixes are pinned against a
real MongoDB.

  • A sealed hierarchy stored as the root entity of a direct collection could be written but never read back. The
    decoder required the _type discriminator to be the document's first field, and the server stores _id first.
    Either and Ior carried their own copies of the same logic and the same defect.
  • Every write threw under an unacknowledged write concern. With w=0 the driver has no result to report, and
    each getter raised UnsupportedOperationException. Results now come back empty.
  • Decimal128 lost precision above 2^53 by being read through Double; the Long range check admitted exactly
    2^63 and saturated; a non-finite value threw ArithmeticException past the Either.
  • An explicit BsonEncoder/BsonDecoder pair and automatic WireCodec derivation were ambiguous, so a case
    class carrying both resolved no codec at all. Derivation is now the lower priority.
  • cats.data.Ior decoded its two sides by position, silently swapping them when the stored field order differed
    and both sides shared a wire type.
  • Sort and Index kept a repeated field's original position, so asc(a).asc(b).desc(a) sorted by a first.
  • insertMany returned ids in the driver's map iteration order rather than by command position.
  • aggregate(...).first appended $limit after $out/$merge, which the server rejects.
  • A projection that mixed inclusion and exclusion silently returned extra fields, and Projection.excludeId
    followed by include silently lost the _id exclusion.
  • WatchOptions' three starting points were not mutually exclusive, so a resume token and an operation time
    could both reach the server, which rejects that.
  • A bare discriminator naming a case that carries fields raised IndexOutOfBoundsException instead of a
    BsonError, and a subtype declaring its own _type field wrote duplicate BSON keys.
  • rapid's MongoClientResource leaked the client when use threw while its task was being built.
  • zio's bracketCase swallowed a finalizer error on success, where every other runtime raises it.
  • kyo's stream failed as a ClassCastException when handed a Streamable it had not derived itself.
  • Reads on the direct path are strict about BSON numeric types — an Int32 where the model says Long is an
    error, unlike the lenient getCollection path. This was always true; it is now pinned by tests and documented.

Known gaps

GridFS, geospatial operators, CreateCollectionOptions, transaction retry semantics and a circe bridge are not
covered. Each has a workaround today, and all but one are additive — see
ROADMAP.md.

Compatibility

Scala 3.3 LTS, except mongo4s-bson-calypso, mongo4s-kyo and mongo4s-rapid, which are pinned to 3.8 by their
upstream dependencies and cannot be consumed from a 3.3 project. mongo4s-kyo sits outside the binary compatibility
promise while kyo is on a release candidate. Tested against JDK 25 and MongoDB 7.