Skip to content

Commit ac0e0ee

Browse files
authored
SWIFT-1070 Improve performance of BSONDecoder and BSONEncoder (#54)
1 parent fdae759 commit ac0e0ee

9 files changed

+99
-103
lines changed

Sources/SwiftBSON/Array+BSONValue.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ extension Array: BSONValue where Element == BSON {
4646
guard let doc = try BSONDocument.read(from: &buffer).documentValue else {
4747
throw BSONError.InternalError(message: "BSON Array cannot be read, failed to get documentValue")
4848
}
49-
return .array(doc.values)
49+
var values: [BSON] = []
50+
let it = doc.makeIterator()
51+
while let (_, val) = try it.nextThrowing() {
52+
values.append(val)
53+
}
54+
return .array(values)
5055
}
5156

5257
internal func write(to buffer: inout ByteBuffer) {
@@ -56,4 +61,10 @@ extension Array: BSONValue where Element == BSON {
5661
}
5762
array.write(to: &buffer)
5863
}
64+
65+
internal func validate() throws {
66+
for v in self {
67+
try v.bsonValue.validate()
68+
}
69+
}
5970
}

Sources/SwiftBSON/BSON.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ extension BSON: ExpressibleByBooleanLiteral {
446446

447447
extension BSON: ExpressibleByDictionaryLiteral {
448448
public init(dictionaryLiteral elements: (String, BSON)...) {
449-
self = .document(BSONDocument(keyValuePairs: elements))
449+
self = .document(BSONDocument(dictionaryLiteral: elements))
450450
}
451451
}
452452

Sources/SwiftBSON/BSONCode.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ extension BSONCodeWithScope: BSONValue {
186186
return .codeWithScope(BSONCodeWithScope(code: code, scope: scope))
187187
}
188188

189+
internal func validate() throws {
190+
try self.scope.validate()
191+
}
192+
189193
internal func write(to buffer: inout ByteBuffer) {
190194
let writer = buffer.writerIndex
191195
buffer.writeInteger(0, endianness: .little, as: Int32.self) // reserve space

Sources/SwiftBSON/BSONDecoder.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -511,16 +511,20 @@ private struct _BSONKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
511511
/// A reference to the decoder we're reading from.
512512
private let decoder: _BSONDecoder
513513

514-
/// A reference to the container we're reading from.
515-
fileprivate let container: BSONDocument
514+
/// An unordered copy of the container we're reading from.
515+
fileprivate let container: [String: BSON]
516516

517517
/// The path of coding keys taken to get to this point in decoding.
518518
public private(set) var codingPath: [CodingKey]
519519

520520
/// Initializes `self`, referencing the given decoder and container.
521-
fileprivate init(referencing decoder: _BSONDecoder, wrapping container: BSONDocument) {
521+
fileprivate init(referencing decoder: _BSONDecoder, wrapping doc: BSONDocument) {
522522
self.decoder = decoder
523-
self.container = container
523+
var map: [String: BSON] = [:]
524+
for (k, v) in doc {
525+
map[k] = v
526+
}
527+
self.container = map
524528
self.codingPath = decoder.codingPath
525529
}
526530

@@ -531,7 +535,7 @@ private struct _BSONKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
531535

532536
/// Returns a Boolean value indicating whether the decoder contains a value associated with the given key.
533537
public func contains(_ key: Key) -> Bool {
534-
self.container.hasKey(key.stringValue)
538+
self.container[key.stringValue] != nil
535539
}
536540

537541
/// A string description of a CodingKey, for use in error messages.

Sources/SwiftBSON/BSONDocument+Sequence.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension BSONDocument: Sequence {
1515
public typealias SubSequence = BSONDocument
1616

1717
/// Returns a `Bool` indicating whether the document is empty.
18-
public var isEmpty: Bool { self.keySet.isEmpty }
18+
public var isEmpty: Bool { self.storage.encodedLength == BSON_MIN_SIZE }
1919

2020
/// Returns a `DocumentIterator` over the values in this `Document`.
2121
public func makeIterator() -> BSONDocumentIterator {
@@ -29,7 +29,11 @@ extension BSONDocument: Sequence {
2929
public func map<T>(
3030
_ transform: (Element) throws -> T
3131
) rethrows -> [T] {
32-
try AnySequence(self).map(transform)
32+
var values: [T] = []
33+
for (k, v) in self {
34+
values.append(try transform((key: k, value: v)))
35+
}
36+
return values
3337
}
3438

3539
/**

Sources/SwiftBSON/BSONDocument.swift

Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,15 @@ public struct BSONDocument {
1515
/// The element type of a document: a tuple containing an individual key-value pair.
1616
public typealias KeyValuePair = (key: String, value: BSON)
1717

18-
private var storage: BSONDocumentStorage
19-
20-
/// An unordered set containing the keys in this document.
21-
internal private(set) var keySet: Set<String>
18+
internal var storage: BSONDocumentStorage
2219

2320
internal init(_ elements: [BSON]) {
2421
self = BSONDocument(keyValuePairs: elements.enumerated().map { i, element in (String(i), element) })
2522
}
2623

2724
internal init(keyValuePairs: [(String, BSON)]) {
28-
self.keySet = Set(keyValuePairs.map { $0.0 })
29-
guard self.keySet.count == keyValuePairs.count else {
30-
fatalError("Dictionary \(keyValuePairs) contains duplicate keys")
31-
}
32-
3325
self.storage = BSONDocumentStorage()
3426

35-
guard !self.keySet.isEmpty else {
36-
self = BSONDocument()
37-
return
38-
}
39-
4027
let start = self.storage.buffer.writerIndex
4128

4229
// reserve space for our byteLength that will be calculated
@@ -58,7 +45,6 @@ public struct BSONDocument {
5845

5946
/// Initializes a new, empty `BSONDocument`.
6047
public init() {
61-
self.keySet = Set()
6248
self.storage = BSONDocumentStorage()
6349
self.storage.buffer.writeInteger(5, endianness: .little, as: Int32.self)
6450
self.storage.buffer.writeBytes([0])
@@ -89,13 +75,12 @@ public struct BSONDocument {
8975
*/
9076
public init(fromBSON bson: ByteBuffer) throws {
9177
let storage = BSONDocumentStorage(bson)
92-
let keys = try storage.validateAndRetrieveKeys()
93-
self = BSONDocument(fromUnsafeBSON: storage, keys: keys)
78+
try storage.validate()
79+
self = BSONDocument(fromUnsafeBSON: storage)
9480
}
9581

96-
internal init(fromUnsafeBSON storage: BSONDocumentStorage, keys: Set<String>) {
82+
internal init(fromUnsafeBSON storage: BSONDocumentStorage) {
9783
self.storage = storage
98-
self.keySet = keys
9984
}
10085

10186
/**
@@ -155,7 +140,13 @@ public struct BSONDocument {
155140
public var values: [BSON] { self.map { _, val in val } }
156141

157142
/// The number of (key, value) pairs stored at the top level of this document.
158-
public var count: Int { self.keySet.count }
143+
public var count: Int {
144+
do {
145+
return try BSONDocumentIterator.getKeys(from: self.storage.buffer).count
146+
} catch {
147+
return 0
148+
}
149+
}
159150

160151
/// A copy of the `ByteBuffer` backing this document, containing raw BSON data. As `ByteBuffer`s implement
161152
/// copy-on-write, this copy will share byte storage with this document until either the document or the returned
@@ -166,7 +157,9 @@ public struct BSONDocument {
166157
public func toData() -> Data { Data(self.storage.buffer.readableBytesView) }
167158

168159
/// Returns a `Boolean` indicating whether this `BSONDocument` contains the provided key.
169-
public func hasKey(_ key: String) -> Bool { self.keySet.contains(key) }
160+
public func hasKey(_ key: String) -> Bool {
161+
(try? BSONDocumentIterator.find(key: key, in: self)) != nil
162+
}
170163

171164
/**
172165
* Allows getting and setting values on the document via subscript syntax.
@@ -236,7 +229,7 @@ public struct BSONDocument {
236229
* - SeeAlso: https://docs.mongodb.com/manual/core/document/#the-id-field
237230
*/
238231
public func withID() throws -> BSONDocument {
239-
guard !self.keySet.contains("_id") else {
232+
guard !self.hasKey("_id") else {
240233
return self
241234
}
242235

@@ -259,37 +252,35 @@ public struct BSONDocument {
259252
}
260253
newStorage.buffer.writeBytes(suffix)
261254

262-
var newKeys = self.keySet
263-
newKeys.insert("_id")
264-
var document = BSONDocument(fromUnsafeBSON: newStorage, keys: newKeys)
255+
var document = BSONDocument(fromUnsafeBSON: newStorage)
265256
document.storage.encodedLength = newSize
266257
return document
267258
}
268259

260+
/// Appends the provided key value pair without checking to see if the key already exists.
261+
/// Warning: appending two of the same key may result in errors or undefined behavior.
262+
internal mutating func append(key: String, value: BSON) {
263+
// setup to overwrite null terminator
264+
self.storage.buffer.moveWriterIndex(to: self.storage.encodedLength - 1)
265+
let size = self.storage.append(key: key, value: value)
266+
self.storage.buffer.writeInteger(0, endianness: .little, as: UInt8.self) // add back in our null terminator
267+
self.storage.encodedLength += size
268+
}
269+
269270
/**
270271
* Sets a BSON element with the corresponding key
271272
* if element.value is nil the element is deleted from the BSON
272273
*/
273274
internal mutating func set(key: String, to value: BSON?) throws {
274-
if !self.keySet.contains(key) {
275+
guard let range = try BSONDocumentIterator.findByteRange(for: key, in: self) else {
275276
guard let value = value else {
276277
// no-op: key does not exist and the value is nil
277278
return
278279
}
279-
// appending new key
280-
self.keySet.insert(key)
281-
// setup to overwrite null terminator
282-
self.storage.buffer.moveWriterIndex(to: self.storage.encodedLength - 1)
283-
let size = self.storage.append(key: key, value: value)
284-
self.storage.buffer.writeInteger(0, endianness: .little, as: UInt8.self) // add back in our null terminator
285-
self.storage.encodedLength += size
280+
self.append(key: key, value: value)
286281
return
287282
}
288283

289-
guard let range = try BSONDocumentIterator.findByteRange(for: key, in: self) else {
290-
throw BSONError.InternalError(message: "Cannot find \(key) to delete")
291-
}
292-
293284
let prefixLength = range.startIndex
294285
let suffixLength = self.storage.encodedLength - range.endIndex
295286

@@ -316,9 +307,6 @@ public struct BSONDocument {
316307
guard newSize <= BSON_MAX_SIZE else {
317308
throw BSONError.DocumentTooLargeError(value: value.bsonValue, forKey: key)
318309
}
319-
} else {
320-
// Deleting
321-
self.keySet.remove(key)
322310
}
323311

324312
newStorage.buffer.writeBytes(suffix)
@@ -401,8 +389,7 @@ public struct BSONDocument {
401389
return totalBytes
402390
}
403391

404-
@discardableResult
405-
internal func validateAndRetrieveKeys() throws -> Set<String> {
392+
internal func validate() throws {
406393
// Pull apart the underlying binary into [KeyValuePair], should reveal issues
407394
guard let encodedLength = self.buffer.getInteger(at: 0, endianness: .little, as: Int32.self) else {
408395
throw BSONError.InvalidArgumentError(message: "Validation Failed: Cannot read encoded length")
@@ -423,7 +410,6 @@ public struct BSONDocument {
423410

424411
var keySet = Set<String>()
425412
let iter = BSONDocumentIterator(over: self.buffer)
426-
// Implicitly validate with iterator
427413
do {
428414
while let (key, value) = try iter.nextThrowing() {
429415
let (inserted, _) = keySet.insert(key)
@@ -432,26 +418,13 @@ public struct BSONDocument {
432418
message: "Validation Failed: BSON contains multiple values for key \(key)"
433419
)
434420
}
435-
switch value {
436-
case let .document(doc):
437-
try doc.storage.validateAndRetrieveKeys()
438-
case let .array(array):
439-
for item in array {
440-
if let doc = item.documentValue {
441-
try doc.storage.validateAndRetrieveKeys()
442-
}
443-
}
444-
default:
445-
continue
446-
}
421+
try value.bsonValue.validate()
447422
}
448423
} catch let error as BSONError.InternalError {
449424
throw BSONError.InvalidArgumentError(
450425
message: "Validation Failed: \(error.message)"
451426
)
452427
}
453-
454-
return keySet
455428
}
456429
}
457430
}
@@ -469,6 +442,13 @@ extension BSONDocument: ExpressibleByDictionaryLiteral {
469442
* - Returns: a new `BSONDocument`
470443
*/
471444
public init(dictionaryLiteral keyValuePairs: (String, BSON)...) {
445+
self.init(dictionaryLiteral: Array(keyValuePairs))
446+
}
447+
448+
internal init(dictionaryLiteral keyValuePairs: [(String, BSON)]) {
449+
guard Set(keyValuePairs.map { $0.0 }).count == keyValuePairs.count else {
450+
fatalError("Dictionary \(keyValuePairs) contains duplicate keys")
451+
}
472452
self.init(keyValuePairs: keyValuePairs)
473453
}
474454
}
@@ -548,13 +528,16 @@ extension BSONDocument: BSONValue {
548528
throw BSONError.InternalError(message: "Cannot read document contents")
549529
}
550530

551-
let keys = try BSONDocumentIterator.getKeySet(from: bytes)
552-
return .document(BSONDocument(fromUnsafeBSON: BSONDocument.BSONDocumentStorage(bytes), keys: keys))
531+
return .document(BSONDocument(fromUnsafeBSON: BSONDocument.BSONDocumentStorage(bytes)))
553532
}
554533

555534
internal func write(to buffer: inout ByteBuffer) {
556535
buffer.writeBytes(self.storage.buffer.readableBytesView)
557536
}
537+
538+
internal func validate() throws {
539+
try self.storage.validate()
540+
}
558541
}
559542

560543
extension BSONDocument: CustomStringConvertible {
@@ -575,7 +558,7 @@ extension BSONDocument {
575558
* - Returns: a `Bool` indicating whether the two documents are equal.
576559
*/
577560
public func equalsIgnoreKeyOrder(_ other: BSONDocument) -> Bool {
578-
guard self.count == other.count else {
561+
guard self.storage.encodedLength == other.storage.encodedLength else {
579562
return false
580563
}
581564

0 commit comments

Comments
 (0)