@@ -41,6 +41,10 @@ public class MongoCursor<T: Codable>: CursorProtocol {
4141 /// Decoder from the client, database, or collection that created this cursor.
4242 internal let decoder : BSONDecoder
4343
44+ /// The ID used by the server to track the cursor over time. If all of the cursor's results were returnable in a
45+ /// single batch, or if the cursor contained no results, this value will be nil.
46+ public let id : Int64 ?
47+
4448 /**
4549 * Initializes a new `MongoCursor` instance. Not meant to be instantiated directly by a user. When `forceIO` is
4650 * true, this initializer will force a connection to the server if one is not already established.
@@ -66,11 +70,14 @@ public class MongoCursor<T: Codable>: CursorProtocol {
6670 session: session,
6771 type: cursorType ?? . nonTailable
6872 )
69- }
7073
71- /// Asserts that the cursor was closed.
72- deinit {
73- assert ( !self . isAlive, " cursor wasn't closed before it went out of scope " )
74+ self . id = self . wrappedCursor. withUnsafeMongocPointer { ptr in
75+ guard let ptr = ptr else {
76+ return nil
77+ }
78+ let id = mongoc_cursor_get_id ( ptr)
79+ return id == 0 ? nil : id
80+ }
7481 }
7582
7683 /// Decodes a result to the generic type or `nil` if no result were returned.
@@ -89,25 +96,16 @@ public class MongoCursor<T: Codable>: CursorProtocol {
8996 /**
9097 * Indicates whether this cursor has the potential to return more data.
9198 *
92- * This property is mainly useful if this cursor is tailable, since in that case `tryNext` may return more results
99+ * This method is mainly useful if this cursor is tailable, since in that case `tryNext` may return more results
93100 * even after returning `nil`.
94101 *
95102 * If this cursor is non-tailable, it will always be dead as soon as either `tryNext` returns `nil` or an error.
96103 *
97104 * This cursor will be dead as soon as `next` returns `nil` or an error, regardless of the `CursorType`.
98105 */
99- public var isAlive : Bool {
100- return self . wrappedCursor. isAlive
101- }
102-
103- /// Returns the ID used by the server to track the cursor. `nil` once all results have been fetched from the server.
104- public var id : Int64 ? {
105- return self . wrappedCursor. withUnsafeMongocPointer { ptr in
106- guard let ptr = ptr else {
107- return nil
108- }
109- let id = mongoc_cursor_get_id ( ptr)
110- return id == 0 ? nil : id
106+ public func isAlive( ) -> EventLoopFuture < Bool > {
107+ return self . client. operationExecutor. execute {
108+ self . wrappedCursor. isAlive
111109 }
112110 }
113111
@@ -120,6 +118,9 @@ public class MongoCursor<T: Codable>: CursorProtocol {
120118 * before evaluating to `nil`. This option can be configured via options passed to the method that created this
121119 * cursor (e.g. the `maxAwaitTimeMS` option on the `FindOptions` passed to `find`).
122120 *
121+ * Note: You *must not* call any cursor methods besides `kill` and `isAlive` while the future returned from this
122+ * method is unresolved. Doing so will result in undefined behavior.
123+ *
123124 * - Returns:
124125 * An `EventLoopFuture<T?>` containing the next `T` in this cursor, an error if one occurred, or `nil` if
125126 * there was no data.
@@ -143,6 +144,14 @@ public class MongoCursor<T: Codable>: CursorProtocol {
143144 * If this cursor is tailable, this method will continue polling until a non-empty batch is returned from the server
144145 * or the cursor is closed.
145146 *
147+ * A thread from the driver's internal thread pool will be occupied until the returned future is completed, so
148+ * performance degradation is possible if the number of polling cursors is too close to the total number of threads
149+ * in the thread pool. To configure the total number of threads in the pool, set the `ClientOptions.threadPoolSize`
150+ * option during client creation.
151+ *
152+ * Note: You *must not* call any cursor methods besides `kill` and `isAlive` while the future returned from this
153+ * method is unresolved. Doing so will result in undefined behavior.
154+ *
146155 * - Returns:
147156 * An `EventLoopFuture<T?>` evaluating to the next `T` in this cursor, or `nil` if the cursor is exhausted. If
148157 * the underlying cursor is tailable, the future will not resolve until data is returned (potentially after
@@ -168,6 +177,9 @@ public class MongoCursor<T: Codable>: CursorProtocol {
168177 * If this cursor is tailable, `toArray` will only fetch the currently available results, and it
169178 * may return more data if it is called again while the cursor is still alive.
170179 *
180+ * Note: You *must not* call any cursor methods besides `kill` and `isAlive` while the future returned from this
181+ * method is unresolved. Doing so will result in undefined behavior.
182+ *
171183 * - Returns:
172184 * An `EventLoopFuture<[T]>` evaluating to the results currently available in this cursor, or an error.
173185 *
@@ -183,6 +195,39 @@ public class MongoCursor<T: Codable>: CursorProtocol {
183195 }
184196 }
185197
198+ /**
199+ * Calls the provided closure with each element in the cursor.
200+ *
201+ * If the cursor is not tailable, this method will exhaust it, calling the closure with every document.
202+ *
203+ * If the cursor is tailable, the method will call the closure with each new document as it arrives.
204+ *
205+ * A thread from the driver's internal thread pool will be occupied until the returned future is completed, so
206+ * performance degradation is possible if the number of polling cursors is too close to the total number of threads
207+ * in the thread pool. To configure the total number of threads in the pool, set the `ClientOptions.threadPoolSize`
208+ * option during client creation.
209+ *
210+ * Note: You *must not* call any cursor methods besides `kill` and `isAlive` while the future returned from this
211+ * method is unresolved. Doing so will result in undefined behavior.
212+ *
213+ * - Returns:
214+ * An `EventLoopFuture<Void>` which will succeed when the end of the cursor is reached, or in the case of a
215+ * tailable cursor, when the cursor is killed via `kill`.
216+ *
217+ * If the future evaluates to an error, that error is likely one of the following:
218+ * - `CommandError` if an error occurs while fetching more results from the server.
219+ * - `LogicError` if this function is called after the cursor has died.
220+ * - `LogicError` if this function is called and the session associated with this cursor is inactive.
221+ * - `DecodingError` if an error occurs decoding the server's responses.
222+ */
223+ public func forEach( _ body: @escaping ( T ) throws -> Void ) -> EventLoopFuture < Void > {
224+ return self . client. operationExecutor. execute {
225+ while let next = try self . decode ( result: self . wrappedCursor. next ( ) ) {
226+ try body ( next)
227+ }
228+ }
229+ }
230+
186231 /**
187232 * Kill this cursor.
188233 *
0 commit comments