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:
- Add the driver to your
Package.swift
file - Run
swift package generate-xcodeproj
from the command line - 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 struct
s
Like many Swift libraries, the driver previously used enum
s 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 ChangeStream
s and tailable MongoCursor
s. (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 MongoCursor
s, 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 struct
s, and extracting the right event type required lots of downcasting. Starting in this release, common event types are grouped into enum
s, namely into the SDAMEvent
and CommandEvent
enum
s, whose cases’ associated values are the existing event struct
s. 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>
inMongoCursor
- [SWIFT-688] - Iterate over
Result<T>
inChangeStream
- [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