Skip to content

Commit 50f6686

Browse files
committed
move DocumentIterator into its own file
1 parent 2aff76f commit 50f6686

File tree

2 files changed

+170
-168
lines changed

2 files changed

+170
-168
lines changed

Sources/MongoSwift/BSON/Document+Sequence.swift

Lines changed: 5 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,19 @@ extension Document: Sequence {
190190
*
191191
* - Parameters:
192192
* - maxSplits: The maximum number of times to split the document, or one less than the number of subsequences to
193-
* return. If `maxSplits` + 1 subsequences are returned, the last one is a suffix of the original
193+
* return. If `maxSplits` + 1 subsequences are returned, the last one is a suffix of the original
194194
* document containing the remaining key-value pairs. `maxSplits` must be greater than or equal to
195195
* zero. The default value is `Int.max`.
196-
* - omittingEmptySubsequences: If false, an empty document is returned in the result for each pair of
196+
* - omittingEmptySubsequences: If false, an empty document is returned in the result for each pair of
197197
* consecutive key-value pairs satisfying the `isSeparator` predicate and for each
198-
* key-value pair at the start or end of the document satisfying the `isSeparator`
198+
* key-value pair at the start or end of the document satisfying the `isSeparator`
199199
* predicate. If true, only nonempty documents are returned. The default value is
200200
* true.
201201
* - isSeparator: A closure that returns true if its argument should be used to split the document and otherwise
202202
* returns false.
203203
*
204204
* - Returns: An array of documents, split from this document's key-value pairs.
205-
*/
205+
*/
206206
public func split(maxSplits: Int = Int.max,
207207
omittingEmptySubsequences: Bool = true,
208208
whereSeparator isSeparator: (KeyValuePair) throws -> Bool) rethrows -> [Document] {
@@ -226,7 +226,7 @@ extension Document: Sequence {
226226
}
227227

228228
extension Document {
229-
// this is an alternative to the built-in `Document.filter` that returns an `[KeyValuePair]`.
229+
// this is an alternative to the built-in `Document.filter` that returns an `[KeyValuePair]`.
230230
// this variant is called by default, but the other is still accessible by explicitly stating
231231
// return type: `let newDocPairs: [Document.KeyValuePair] = newDoc.filter { ... }`
232232
/**
@@ -248,166 +248,3 @@ extension Document {
248248
return output
249249
}
250250
}
251-
252-
/// An iterator over the values in a `Document`.
253-
public class DocumentIterator: IteratorProtocol {
254-
/// the libbson iterator. it must be a `var` because we use it as
255-
/// an inout argument
256-
internal var iter: bson_iter_t
257-
/// a reference to the storage for the document we're iterating
258-
internal let storage: DocumentStorage
259-
260-
/// Initializes a new iterator over the contents of `doc`. Returns `nil` if the key is not
261-
/// found, or if an iterator cannot be created over `doc` due to an error from e.g. corrupt data.
262-
internal init?(forDocument doc: Document) {
263-
self.iter = bson_iter_t()
264-
self.storage = doc.storage
265-
guard bson_iter_init(&self.iter, doc.data) else {
266-
return nil
267-
}
268-
}
269-
270-
/// Initializes a new iterator over the contents of `doc`. Returns `nil` if an iterator cannot
271-
/// be created over `doc` due to an error from e.g. corrupt data, or if the key is not found.
272-
internal init?(forDocument doc: Document, advancedTo key: String) {
273-
self.iter = bson_iter_t()
274-
self.storage = doc.storage
275-
guard bson_iter_init_find(&iter, doc.data, key.cString(using: .utf8)) else {
276-
return nil
277-
}
278-
}
279-
280-
/// Advances the iterator forward one value. Returns false if there is an error moving forward
281-
/// or if at the end of the document. Returns true otherwise.
282-
internal func advance() -> Bool {
283-
return bson_iter_next(&self.iter)
284-
}
285-
286-
/// Moves the iterator to the specified key. Returns false if the key does not exist. Returns true otherwise.
287-
internal func move(to key: String) -> Bool {
288-
return bson_iter_find(&self.iter, key.cString(using: .utf8))
289-
}
290-
291-
/// Returns the current key. Assumes the iterator is in a valid position.
292-
internal var currentKey: String {
293-
return String(cString: bson_iter_key(&self.iter))
294-
}
295-
296-
/// Returns the current value. Assumes the iterator is in a valid position.
297-
internal var currentValue: BSONValue {
298-
do {
299-
return try self.safeCurrentValue()
300-
} catch { // Since properties cannot throw, we need to catch and raise a fatalError.
301-
fatalError("Error getting current value from iterator: \(error)")
302-
}
303-
}
304-
305-
/// Returns the current value's type. Assumes the iterator is in a valid position.
306-
internal var currentType: BSONType {
307-
return BSONType(rawValue: bson_iter_type(&self.iter).rawValue) ?? .invalid
308-
}
309-
310-
/// Returns the keys from the iterator's current position to the end. The iterator
311-
/// will be exhausted after this property is accessed.
312-
internal var keys: [String] {
313-
var keys = [String]()
314-
while self.advance() { keys.append(self.currentKey) }
315-
return keys
316-
}
317-
318-
/// Returns the values from the iterator's current position to the end. The iterator
319-
/// will be exhausted after this property is accessed.
320-
internal var values: [BSONValue] {
321-
var values = [BSONValue]()
322-
while self.advance() { values.append(self.currentValue) }
323-
return values
324-
}
325-
326-
/// Returns the current value (equivalent to the `currentValue` property) or throws on error.
327-
///
328-
/// - Throws:
329-
/// - `RuntimeError.internalError` if the current value of this `DocumentIterator` cannot be decoded to BSON.
330-
internal func safeCurrentValue() throws -> BSONValue {
331-
guard let bsonType = DocumentIterator.bsonTypeMap[currentType] else {
332-
throw RuntimeError.internalError(
333-
message: "Unknown BSONType for iterator's current value with type: \(currentType)"
334-
)
335-
}
336-
337-
return try bsonType.from(iterator: self)
338-
}
339-
340-
// uses an iterator to copy (key, value) pairs of the provided document from range [startIndex, endIndex) into a new
341-
// document. starts at the startIndex-th pair and ends at the end of the document or the (endIndex-1)th index,
342-
// whichever comes first.
343-
internal static func subsequence(of doc: Document, startIndex: Int = 0, endIndex: Int = Int.max) -> Document {
344-
guard endIndex >= startIndex else {
345-
fatalError("endIndex must be >= startIndex")
346-
}
347-
348-
guard let iter = DocumentIterator(forDocument: doc) else {
349-
return [:]
350-
}
351-
352-
// skip the values preceding startIndex. this is more performant than calling next, because
353-
// it doesn't pull the unneeded key/values out of the iterator
354-
for _ in 0..<startIndex { _ = iter.advance() }
355-
356-
var output = Document()
357-
358-
// TODO SWIFT-224: use va_list variant of bson_copy_to_excluding to improve performance
359-
for _ in startIndex..<endIndex {
360-
if let next = iter.next() {
361-
output[next.key] = next.value
362-
} else {
363-
// we ran out of values
364-
break
365-
}
366-
}
367-
368-
return output
369-
}
370-
371-
/// Returns the next value in the sequence, or `nil` if the iterator is exhausted.
372-
public func next() -> Document.KeyValuePair? {
373-
return self.advance() ? (self.currentKey, self.currentValue) : nil
374-
}
375-
376-
/**
377-
* Overwrites the current value of this `DocumentIterator` with the supplied value.
378-
*
379-
* - Throws:
380-
* - `RuntimeError.internalError` if the new value is an `Int` and cannot be written to BSON.
381-
* - `UserError.logicError` if the new value is a `Decimal128` or `ObjectId` and is improperly formatted.
382-
*/
383-
internal func overwriteCurrentValue(with newValue: Overwritable) throws {
384-
guard newValue.bsonType == self.currentType else {
385-
fatalError("Expected \(newValue) to have BSON type \(self.currentType), but has type \(newValue.bsonType)")
386-
}
387-
try newValue.writeToCurrentPosition(of: self)
388-
}
389-
390-
private static let bsonTypeMap: [BSONType: BSONValue.Type] = [
391-
.double: Double.self,
392-
.string: String.self,
393-
.document: Document.self,
394-
.array: [BSONValue].self,
395-
.binary: Binary.self,
396-
.objectId: ObjectId.self,
397-
.boolean: Bool.self,
398-
.dateTime: Date.self,
399-
.regularExpression: RegularExpression.self,
400-
.dbPointer: DBPointer.self,
401-
.javascript: CodeWithScope.self,
402-
.symbol: Symbol.self,
403-
.javascriptWithScope: CodeWithScope.self,
404-
.int32: Int.self,
405-
.timestamp: Timestamp.self,
406-
.int64: Int64.self,
407-
.decimal128: Decimal128.self,
408-
.minKey: MinKey.self,
409-
.maxKey: MaxKey.self,
410-
.null: BSONNull.self,
411-
.undefined: BSONUndefined.self
412-
]
413-
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import Foundation
2+
import mongoc
3+
4+
/// An iterator over the values in a `Document`.
5+
public class DocumentIterator: IteratorProtocol {
6+
/// the libbson iterator. it must be a `var` because we use it as
7+
/// an inout argument
8+
internal var iter: bson_iter_t
9+
/// a reference to the storage for the document we're iterating
10+
internal let storage: DocumentStorage
11+
12+
/// Initializes a new iterator over the contents of `doc`. Returns `nil` if the key is not
13+
/// found, or if an iterator cannot be created over `doc` due to an error from e.g. corrupt data.
14+
internal init?(forDocument doc: Document) {
15+
self.iter = bson_iter_t()
16+
self.storage = doc.storage
17+
guard bson_iter_init(&self.iter, doc.data) else {
18+
return nil
19+
}
20+
}
21+
22+
/// Initializes a new iterator over the contents of `doc`. Returns `nil` if an iterator cannot
23+
/// be created over `doc` due to an error from e.g. corrupt data, or if the key is not found.
24+
internal init?(forDocument doc: Document, advancedTo key: String) {
25+
self.iter = bson_iter_t()
26+
self.storage = doc.storage
27+
guard bson_iter_init_find(&iter, doc.data, key.cString(using: .utf8)) else {
28+
return nil
29+
}
30+
}
31+
32+
/// Advances the iterator forward one value. Returns false if there is an error moving forward
33+
/// or if at the end of the document. Returns true otherwise.
34+
internal func advance() -> Bool {
35+
return bson_iter_next(&self.iter)
36+
}
37+
38+
/// Moves the iterator to the specified key. Returns false if the key does not exist. Returns true otherwise.
39+
internal func move(to key: String) -> Bool {
40+
return bson_iter_find(&self.iter, key.cString(using: .utf8))
41+
}
42+
43+
/// Returns the current key. Assumes the iterator is in a valid position.
44+
internal var currentKey: String {
45+
return String(cString: bson_iter_key(&self.iter))
46+
}
47+
48+
/// Returns the current value. Assumes the iterator is in a valid position.
49+
internal var currentValue: BSONValue {
50+
do {
51+
return try self.safeCurrentValue()
52+
} catch { // Since properties cannot throw, we need to catch and raise a fatalError.
53+
fatalError("Error getting current value from iterator: \(error)")
54+
}
55+
}
56+
57+
/// Returns the current value's type. Assumes the iterator is in a valid position.
58+
internal var currentType: BSONType {
59+
return BSONType(rawValue: bson_iter_type(&self.iter).rawValue) ?? .invalid
60+
}
61+
62+
/// Returns the keys from the iterator's current position to the end. The iterator
63+
/// will be exhausted after this property is accessed.
64+
internal var keys: [String] {
65+
var keys = [String]()
66+
while self.advance() { keys.append(self.currentKey) }
67+
return keys
68+
}
69+
70+
/// Returns the values from the iterator's current position to the end. The iterator
71+
/// will be exhausted after this property is accessed.
72+
internal var values: [BSONValue] {
73+
var values = [BSONValue]()
74+
while self.advance() { values.append(self.currentValue) }
75+
return values
76+
}
77+
78+
/// Returns the current value (equivalent to the `currentValue` property) or throws on error.
79+
///
80+
/// - Throws:
81+
/// - `RuntimeError.internalError` if the current value of this `DocumentIterator` cannot be decoded to BSON.
82+
internal func safeCurrentValue() throws -> BSONValue {
83+
guard let bsonType = DocumentIterator.bsonTypeMap[currentType] else {
84+
throw RuntimeError.internalError(
85+
message: "Unknown BSONType for iterator's current value with type: \(currentType)"
86+
)
87+
}
88+
89+
return try bsonType.from(iterator: self)
90+
}
91+
92+
// uses an iterator to copy (key, value) pairs of the provided document from range [startIndex, endIndex) into a new
93+
// document. starts at the startIndex-th pair and ends at the end of the document or the (endIndex-1)th index,
94+
// whichever comes first.
95+
internal static func subsequence(of doc: Document, startIndex: Int = 0, endIndex: Int = Int.max) -> Document {
96+
guard endIndex >= startIndex else {
97+
fatalError("endIndex must be >= startIndex")
98+
}
99+
100+
guard let iter = DocumentIterator(forDocument: doc) else {
101+
return [:]
102+
}
103+
104+
// skip the values preceding startIndex. this is more performant than calling next, because
105+
// it doesn't pull the unneeded key/values out of the iterator
106+
for _ in 0..<startIndex { _ = iter.advance() }
107+
108+
var output = Document()
109+
110+
// TODO SWIFT-224: use va_list variant of bson_copy_to_excluding to improve performance
111+
for _ in startIndex..<endIndex {
112+
if let next = iter.next() {
113+
output[next.key] = next.value
114+
} else {
115+
// we ran out of values
116+
break
117+
}
118+
}
119+
120+
return output
121+
}
122+
123+
/// Returns the next value in the sequence, or `nil` if the iterator is exhausted.
124+
public func next() -> Document.KeyValuePair? {
125+
return self.advance() ? (self.currentKey, self.currentValue) : nil
126+
}
127+
128+
/**
129+
* Overwrites the current value of this `DocumentIterator` with the supplied value.
130+
*
131+
* - Throws:
132+
* - `RuntimeError.internalError` if the new value is an `Int` and cannot be written to BSON.
133+
* - `UserError.logicError` if the new value is a `Decimal128` or `ObjectId` and is improperly formatted.
134+
*/
135+
internal func overwriteCurrentValue(with newValue: Overwritable) throws {
136+
guard newValue.bsonType == self.currentType else {
137+
fatalError("Expected \(newValue) to have BSON type \(self.currentType), but has type \(newValue.bsonType)")
138+
}
139+
try newValue.writeToCurrentPosition(of: self)
140+
}
141+
142+
private static let bsonTypeMap: [BSONType: BSONValue.Type] = [
143+
.double: Double.self,
144+
.string: String.self,
145+
.document: Document.self,
146+
.array: [BSONValue].self,
147+
.binary: Binary.self,
148+
.objectId: ObjectId.self,
149+
.boolean: Bool.self,
150+
.dateTime: Date.self,
151+
.regularExpression: RegularExpression.self,
152+
.dbPointer: DBPointer.self,
153+
.javascript: CodeWithScope.self,
154+
.symbol: Symbol.self,
155+
.javascriptWithScope: CodeWithScope.self,
156+
.int32: Int.self,
157+
.timestamp: Timestamp.self,
158+
.int64: Int64.self,
159+
.decimal128: Decimal128.self,
160+
.minKey: MinKey.self,
161+
.maxKey: MaxKey.self,
162+
.null: BSONNull.self,
163+
.undefined: BSONUndefined.self
164+
]
165+
}

0 commit comments

Comments
 (0)