diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b24b020ca..97d107b078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception: ## Next Release - **New**: Added `QueryInterfaceRequest.deleteAndFetchIds(_:)` which returns the set of deleted ids. +- **New**: Added `Set` methods `union`, `formUnion`, `intersection` and `formIntersection` that accept a cursor. ## 6.27.0 diff --git a/GRDB/Core/Cursor.swift b/GRDB/Core/Cursor.swift index b8ee021552..af874297c1 100644 --- a/GRDB/Core/Cursor.swift +++ b/GRDB/Core/Cursor.swift @@ -195,6 +195,56 @@ extension Set { // database cursors. try cursor.forEach { insert($0) } } + + /// Returns a new set with the elements of both this set and the + /// given cursor. + /// + /// If the set already contains one or more elements that are also in + /// the cursor, the existing members are kept. If the cursor contains + /// multiple instances of equivalent elements, only the first instance + /// is kept. + /// + /// - parameter cursor: A cursor of elements. + /// - returns: A new set with the unique elements of this set + /// and `cursor`. + public func union(_ cursor: some Cursor) throws -> Set { + var result = self + try result.formUnion(cursor) + return result + } + + /// Inserts the elements of the given cursor into the set. + /// + /// If the set already contains one or more elements that are also in + /// the cursor, the existing members are kept. If the cursor contains + /// multiple instances of equivalent elements, only the first instance + /// is kept. + /// + /// - parameter cursor: A cursor of elements. + public mutating func formUnion(_ cursor: some Cursor) throws { + while let element = try cursor.next() { + insert(element) + } + } + + /// Returns a new set with the elements that are common to both this set + /// and the given cursor. + /// + /// - parameter cursor: A cursor of elements. + /// - returns: A new set. + public func intersection(_ cursor: some Cursor) throws -> Set { + var result = self + try result.formIntersection(cursor) + return result + } + + /// Removes the elements of the set that aren’t also in the + /// given cursor. + /// + /// - parameter cursor: A cursor of elements. + public mutating func formIntersection(_ cursor: some Cursor) throws { + try formIntersection(Set(cursor)) + } } extension Sequence { diff --git a/Tests/GRDBTests/CursorTests.swift b/Tests/GRDBTests/CursorTests.swift index 29930f2232..01c104d8c6 100644 --- a/Tests/GRDBTests/CursorTests.swift +++ b/Tests/GRDBTests/CursorTests.swift @@ -223,6 +223,32 @@ class CursorTests: GRDBTestCase { let set = try Set(cursor, minimumCapacity: 100) XCTAssertEqual(set, [1, 2, 3]) } + + do { + let cursor = AnyCursor([1, 2, 1, 3]) + let set: Set = [1, 4] + try XCTAssertEqual(set.union(cursor), [1, 2, 3, 4]) + } + + do { + let cursor = AnyCursor([1, 2, 1, 3]) + var set: Set = [1, 4] + try set.formUnion(cursor) + XCTAssertEqual(set, [1, 2, 3, 4]) + } + + do { + let cursor = AnyCursor([1, 2, 1, 3]) + let set: Set = [1, 3, 4] + try XCTAssertEqual(set.intersection(cursor), [1, 3]) + } + + do { + let cursor = AnyCursor([1, 2, 1, 3]) + var set: Set = [1, 3, 4] + try set.formIntersection(cursor) + XCTAssertEqual(set, [1, 3]) + } } func testDictionaryGrouping() throws {