Skip to content

Commit f2b600e

Browse files
authored
SWIFT-672 Make MongoSwift.MongoCursor async, implement MongoSwiftSync.MongoCursor (#392)
1 parent 3988800 commit f2b600e

38 files changed

+1443
-586
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import NIO
2+
3+
/// A protocol describing the common behavior between cursor-like objects in the driver.
4+
internal protocol Cursor {
5+
/// The decoded type iterated over by the cursor.
6+
associatedtype T: Codable
7+
8+
/**
9+
* Indicates whether this cursor has the potential to return more data.
10+
*
11+
* This property is mainly useful if this cursor is tailable, since in that case `tryNext` may return more results
12+
* even after returning `nil`.
13+
*
14+
* If this cursor is non-tailable, it will always be dead as soon as either `tryNext` returns `nil` or an error.
15+
*
16+
* This cursor will be dead as soon as `next` returns `nil` or an error, regardless of the `CursorType`.
17+
*/
18+
var isAlive: Bool { get }
19+
20+
/**
21+
* Get the next `T` from the cursor.
22+
*
23+
* If this cursor is tailable, this method will continue retrying until a non-empty batch is returned or the cursor
24+
* is closed
25+
*/
26+
func next() -> EventLoopFuture<T?>
27+
28+
/**
29+
* Attempt to get the next `T` from the cursor, returning `nil` if there are no results.
30+
*
31+
* If this cursor is tailable and `isAlive` is true, this may be called multiple times to attempt to retrieve more
32+
* elements.
33+
*
34+
* If this cursor is a tailable await cursor, it will wait for results server side for a maximum of `maxAwaitTimeMS`
35+
* before evaluating to `nil`. This option can be configured via options passed to the method that created this
36+
* cursor (e.g. the `maxAwaitTimeMS` option on the `FindOptions` passed to `find`).
37+
*/
38+
func tryNext() -> EventLoopFuture<T?>
39+
40+
/**
41+
* Kills this cursor.
42+
*
43+
* This method MUST be called before this cursor goes out of scope to prevent leaking resources.
44+
* This method may be called even if there are unresolved futures created from other `Cursor` methods.
45+
*
46+
* This method should not fail.
47+
*/
48+
func kill() -> EventLoopFuture<Void>
49+
}
50+
51+
extension EventLoopFuture {
52+
/// Run the provided callback after this future succeeds, preserving the succeeded value.
53+
internal func afterSuccess(f: @escaping (Value) -> EventLoopFuture<Void>) -> EventLoopFuture<Value> {
54+
return self.flatMap { value in
55+
f(value).and(value: value)
56+
}.map { _, value in
57+
value
58+
}
59+
}
60+
}

Sources/MongoSwift/MongoCollection+Indexes.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,9 @@ extension MongoCollection {
362362
*
363363
* - Throws: `LogicError` if the provided session is inactive.
364364
*/
365-
public func listIndexes(session: ClientSession? = nil) throws -> MongoCursor<IndexModel> {
365+
public func listIndexes(session: ClientSession? = nil) -> EventLoopFuture<MongoCursor<IndexModel>> {
366366
let operation = ListIndexesOperation(collection: self)
367-
return try self._client.executeOperation(operation, session: session)
367+
return self._client.operationExecutor.execute(operation, client: self._client, session: session)
368368
}
369369

370370
/**
@@ -373,19 +373,20 @@ extension MongoCollection {
373373
* - Parameters:
374374
* - session: Optional `ClientSession` to use when executing this command
375375
*
376-
* - Returns: A `MongoCursor` over the index names.
376+
* - Returns: An `EventLoopFuture<[String]>` containing the index names.
377377
*
378378
* - Throws: `LogicError` if the provided session is inactive.
379379
*/
380-
public func listIndexNames(session: ClientSession? = nil) throws -> [String] {
381-
let operation = ListIndexesOperation(collection: self)
382-
let models = try self._client.executeOperation(operation, session: session)
383-
let names: [String] = try models.all().map { model in
384-
guard let name = model.options?.name else {
385-
throw InternalError(message: "Server response missing a 'name' field")
380+
public func listIndexNames(session _: ClientSession? = nil) throws -> EventLoopFuture<[String]> {
381+
return self.listIndexes().flatMap { cursor in
382+
cursor.all()
383+
}.flatMapThrowing { models in
384+
try models.map { model in
385+
guard let name = model.options?.name else {
386+
throw InternalError(message: "Server response missing a 'name' field")
387+
}
388+
return name
386389
}
387-
return name
388390
}
389-
return names
390391
}
391392
}

Sources/MongoSwift/MongoCollection+Read.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ extension MongoCollection {
2222
_ filter: Document = [:],
2323
options: FindOptions? = nil,
2424
session: ClientSession? = nil
25-
) throws -> MongoCursor<CollectionType> {
25+
) -> EventLoopFuture<MongoCursor<CollectionType>> {
2626
let operation = FindOperation(collection: self, filter: filter, options: options)
27-
return try self._client.executeOperation(operation, session: session)
27+
return self._client.operationExecutor.execute(operation, client: self._client, session: session)
2828
}
2929

3030
/**
@@ -46,10 +46,11 @@ extension MongoCollection {
4646
_ filter: Document = [:],
4747
options: FindOneOptions? = nil,
4848
session: ClientSession? = nil
49-
) throws -> T? {
49+
) throws -> EventLoopFuture<T?> {
5050
let options = options.map { FindOptions(from: $0) }
51-
let cursor = try self.find(filter, options: options, session: session)
52-
return try cursor.next()?.get()
51+
return self.find(filter, options: options, session: session).flatMap { cursor in
52+
cursor.next().afterSuccess { _ in cursor.kill() }
53+
}
5354
}
5455

5556
/**
@@ -71,9 +72,9 @@ extension MongoCollection {
7172
_ pipeline: [Document],
7273
options: AggregateOptions? = nil,
7374
session: ClientSession? = nil
74-
) throws -> MongoCursor<Document> {
75+
) -> EventLoopFuture<MongoCursor<Document>> {
7576
let operation = AggregateOperation(collection: self, pipeline: pipeline, options: options)
76-
return try self._client.executeOperation(operation, session: session)
77+
return self._client.operationExecutor.execute(operation, client: self._client, session: session)
7778
}
7879

7980
/**

Sources/MongoSwift/MongoCollection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public struct MongoCollection<T: Codable> {
1919
internal let _client: MongoClient
2020

2121
/// The namespace for this collection.
22-
internal let namespace: MongoNamespace
22+
public let namespace: MongoNamespace
2323

2424
/// Encoder used by this collection for BSON conversions. (e.g. converting `CollectionType`s, indexes, and options
2525
/// to documents).

0 commit comments

Comments
 (0)