MongoDB Swift Driver 1.0.0-rc0 released

MongoSwift 1.0.0-rc0

We are very excited to announce the first release candidate for our upcoming 1.0.0 release.

This release contains a number of major changes to the driver, as detailed in the following sections.

Asynchronous, SwiftNIO-based API

The driver now contains both asynchronous and synchronous APIs for working with MongoDB from Swift.
These APIs are contained in two modules, named MongoSwift (async) and MongoSwiftSync (sync). Depending on which API you would like to use, you can depend on either one of those modules.

The asynchronous API is implemented by running all blocking code off the calling thread in a SwiftNIO NIOThreadPool. The size of this thread pool is configurable via the threadPoolSize property on ClientOptions.

Vapor developers: please note that since we depend on SwiftNIO 2, as of this reelase the driver will not be compatible with Vapor versions < 4, as Vapor 3 depends on SwiftNIO 1.0.

All of the web framework examples in the Examples/ directory of this repository have now been updated to use the asynchronous API.

The synchronous API has been reimplemented as a wrapper of the asynchronous API. You may also configure the size of the thread pool when constructing a synchronous MongoClient as well.

If you are upgrading from a previous version of the driver and would like to continue using the synchronous API, you should update your Package.swift to make your target depend on MongoSwiftSync, and replace every occurrence of import MongoSwift with import MongoSwiftSync.

The MongoDB C driver is now vendored and built via SwiftPM

Previously, the driver would link to a system installation of the MongoDB C driver, libmongoc. We have now vendored the source of libmongoc into the driver, and it is built using SwiftPM.

libmongoc does link to some system libraries itself for e.g. SSL suport, so depending on your operating system and system configuration you may still need to install some libraries. Please see the updated installation instructions for more details.

Note: Unfortunately, due to an issue with the Xcode SwiftPM integration where Xcode ignores cSettings (necessary for building libmongoc), as of Xcode 11.3 the driver currently cannot be added to your project as a dependency in that matter. Please see #387 and SR-12009 for more information. In the meantime, you can work around this by:

  1. Add the driver to your Package.swift file
  2. Run swift package generate-xcodeproj from the command line
  3. Open the resulting .xcodeproj in Xcode

Alternatively, as described in #387 you can clone the driver, run make project from its root directory to generate a corresponding .xcodeproj, and add that to an Xcode workspace.

Driver Error Types are now structs

Like many Swift libraries, the driver previously used enums to represent a number of different error types. However, over time we realized that enums were a poor fit for modeling MongoDB errors.
Anytime we wished to add an additional associated value to one of the error cases in an enum, or to add a new case to one of the enums, it would be a breaking change.
Over time the MongoDB server has added more and more information to the errors it returns, and has added various new categories of errors. Enums made it difficult for our errors to evolve gracefully along with the server.

Now, each type of error that was previously an enum case is represented as a struct, and similar errors are grouped together by protocols rather than by being cases in the same enum.

Please see the updated error handling guide for more information on the types of errors and best practices for working with them.

Synchronous Cursor and Change Stream API Updates

Use of Result

The synchronous variants of MongoCursor and ChangeStream (defined in MongoSwiftSync) now return a Result<T>? from their next() methods rather than a T?.
You can read more about the Swift Standard Library’s Result type here.
This change enables to propagate errors encountered while iterating, for example a network error, via a failed Result. Previously, users had to inspect the error property of a cursor/change stream, which was unintuitive and easy to forget.

Iterating over a cursor would now look like this:

for result in cursor {
    switch result {
    case let .success(doc):
        // do something with doc
    case let .failure(error):
        // handle error    
    }
}

Alternatively, you may use the get method on Result:

for result in cursor {
    let doc = try result.get()
    // do something with doc
}

Since errors are now propagated in this way, the error property has been removed from both types and inspecting it is no longer necessary.

next() now blocks while waiting for more results

This change only affects ChangeStreams and tailable MongoCursors. (See: change streams, tailable cursors.) (By default, cursors are not tailable.)
These types will stay alive even after their initial results have been exhausted, and will continue to receive new matching documents (or events in the case of change streams) if and when they become available.

In the past, next() would simply return nil immediately if a next result was not available. This would require a user who wants to wait for the next result to continuously loop and check for a non-nil result.
Now, next() will internally poll until a new result is obtained or the cursor is killed (you can trigger this yourself by calling kill).

If you wish to use the old behavior where the method would not continuously poll and look for more results, you can use the newly introduced tryNext() which preserves that behavior.

For non-tailable MongoCursors, the cursor is automatically killed as soon as all currently available results are retrieved, so next() will behave exactly the same as tryNext().

Note that a consequence of this change is that working with a tailable MongoCursor or a ChangeStream via Sequence methods or a for loop can block while waiting for new results, since many Sequence methods are implemented via next().

Conformance to LazySequenceProtocol

MongoCursor and ChangeStream now conform to LazySequenceProtocol, which inherits from Sequence (which these types conformed to previously).

This allows the standard library to defer applying operations such as map and filter until the elements of the resulting Sequence are actually accessed. This is beneficial for cursors and change streams as you can transform their elements without having to load the entire result set into memory at once. For example, consider the following snippet. The map call will be lazily applied as each element is read from the cursor in step 3:

// 1. Create a cursor
let cursor = try myCollection.find()

// 2. Add a call to `map` that transforms each result in the cursor by adding a new key
let transformed = cursor.map { result in
    // try to get the result, and if we succeed add a key "a" to it. if we fail, return
    // a failed result containing the error
    Result { () throws -> Document  in
        var doc = try result.get()
        doc["a"] = 1
        return doc
    }
}

// 3. Iterate the transformed cursor
for result in transformed {
    // ...
}

Note: If you wish to take advantage of LazySequenceProtocol, you cannot throw from the closure passed to map / filter / etc. Those variants only exist on Sequence, and calling them will result in the sequence being eagerly loaded into memory before the closure is applied.

Improved Monitoring API

More Flexible Event Handling

Prior to this release, MongoClient posted all monitoring events to a NotificationCenter, either one provided to it via ClientOptions or the application’s default center. This was overly restrictive, as it required you to interface with NotificationCenter in order to receive monitoring events, even if NotificationCenter wasn’t used anywhere else in your application.

Starting in this release, you can attach your own handler types that conform to the new CommandEventHandler and SDAMEventHandler protocols to via MongoClient.addCommandEventHandler and MongoClient.addSDAMEventHandler respectively. The appropriate monitoring events will then be passed to them directly via the protocol requirement methods. From there, you can do whatever processing of the events you want, including, but not limited to, posting to a NotificationCenter.

Also, there are ergonomic overloads for both of the handler adding methods that take in callbacks if you don’t want to define your own handler type:

client.addCommandEventHandler { event in
    print(event)
    // be sure not to strongly capture client in here!
}

Restructured Event Types

Prior to this release, all monitoring events were defined as their own structs, and extracting the right event type required lots of downcasting. Starting in this release, common event types are grouped into enums, namely into the SDAMEvent and CommandEvent enums, whose cases’ associated values are the existing event structs. This models events in a way that makes better use of the Swift type system by removing the need for downcasting, allowing like events to be grouped together, and enabling relevant event types to be switched over exhaustively.

Included Tickets

Bug

  • [SWIFT-663] - Stop parsing write concern errors when extracting bulk write errors

New Features

  • [SWIFT-410] - Async API
  • [SWIFT-643] - Add a findOne method
  • [SWIFT-718] - Implement toArray method for async cursors and change streams
  • [SWIFT-719] - Implement forEach method for async cursors and change streams
  • [SWIFT-360] - More flexible monitoring API

Improvement

  • [SWIFT-500] - Vendor libmongoc and build it with SwiftPM
  • [SWIFT-675] - Iterate over Result<T> in MongoCursor
  • [SWIFT-688] - Iterate over Result<T> in ChangeStream
  • [SWIFT-661] - Make find and aggregate do immediate I/O
  • [SWIFT-314] - Gracefully handle errors parsing isMaster responses for SDAM monitoring
  • [SWIFT-502] - Reimplement errors as structs
  • [SWIFT-601] - Introduce protocol(s) for SDAM monitoring events

Task

  • [SWIFT-288] - Make use of new libmongoc function for getting a server’s lastUpdateTime
  • [SWIFT-427] - Remove autoIndexId option for collection creation
2 Likes

This is great as a SSS fan! :slightly_smiling_face:

3 Likes