diff --git a/Sources/MongoDriver/MongoDriver.swift b/Sources/MongoDriver/MongoDriver.swift index d3364a3..f8af598 100644 --- a/Sources/MongoDriver/MongoDriver.swift +++ b/Sources/MongoDriver/MongoDriver.swift @@ -264,7 +264,7 @@ extension MongoKitten.Database : Fluent.Driver, Connection { let collectionName = lookup.joined.name pipeline.append(.lookup(from: self[lookup.joined.entity], localField: lookup.baseKey, foreignField: lookup.joinedKey, as: collectionName)) - pipeline.append(.unwind("$" + collectionName)) + pipeline.append(.unwind("$" + collectionName, preserveNullAndEmptyArrays: lookup.kind == .outer)) } pipeline.append(.match(filter)) diff --git a/Tests/MongoDriverTests/DriverTests.swift b/Tests/MongoDriverTests/DriverTests.swift index 343a7cc..540d509 100755 --- a/Tests/MongoDriverTests/DriverTests.swift +++ b/Tests/MongoDriverTests/DriverTests.swift @@ -8,6 +8,7 @@ class DriverTests: XCTestCase { return [ ("testInsertAndFind", testInsertAndFind), ("testArray", testArray), + ("testOuterJoin", testOuterJoin), ("testSiblingsCount", testSiblingsCount), ("testMax", testMax), ("testSiblingsMax", testSiblingsMax), @@ -61,6 +62,49 @@ class DriverTests: XCTestCase { XCTAssert(foundStore.vinyls.filter({ return $0.name == "Thriller" }).count == 1) } + func testOuterJoin() throws { + try driver.drop() + let db = Fluent.Database(driver) + + Pet.database = db + Toy.database = db + + let ball = Toy(name: "ball") + let bone = Toy(name: "bone") + let puppet = Toy(name: "puppet") + + try ball.save() + try bone.save() + try puppet.save() + + let molly = Pet(name: "Molly", age: 2) + molly.favoriteToyId = ball.id + + let rex = Pet(name: "Rex", age: 1) + + try molly.save() + try rex.save() + + XCTAssertNotNil(try molly.favoriteToy.get()) + XCTAssertNil(try rex.favoriteToy.get()) + + let toysFavoritedByPets = try Toy.makeQuery() + .join(kind: .inner, Pet.self, baseKey: Toy.idKey, joinedKey: "favoriteToyId") + .all() + + XCTAssertEqual(toysFavoritedByPets.count, 1) + XCTAssertEqual(toysFavoritedByPets.first?.id, ball.id) + + let toysNotFavoritedByPets = try Toy.makeQuery() + .join(kind: .outer, Pet.self, baseKey: Toy.idKey, joinedKey: "favoriteToyId") + .filter(Pet.self, Pet.idKey, .equals, nil) + .all() + + XCTAssertEqual(toysNotFavoritedByPets.count, 2) + XCTAssertTrue(toysNotFavoritedByPets.contains(where: { $0.id == bone.id })) + XCTAssertTrue(toysNotFavoritedByPets.contains(where: { $0.id == puppet.id })) + } + func testSiblingsCount() throws { try driver.drop() let db = Fluent.Database(driver) diff --git a/Tests/MongoDriverTests/Pet.swift b/Tests/MongoDriverTests/Pet.swift index dc0e82a..f4fdd59 100644 --- a/Tests/MongoDriverTests/Pet.swift +++ b/Tests/MongoDriverTests/Pet.swift @@ -14,9 +14,12 @@ final class Pet: Entity { public let age: Int - public init(name: String, age: Int) { + public var favoriteToyId: Identifier? + + public init(name: String, age: Int, favoriteToyId: Identifier? = nil) { self.name = name self.age = age + self.favoriteToyId = favoriteToyId } // MARK: Storable @@ -27,7 +30,11 @@ final class Pet: Entity { public convenience init(row: Row) throws { - self.init(name: try row.get("name"), age: try row.get("age")) + self.init( + name: try row.get("name"), + age: try row.get("age"), + favoriteToyId: try row.get("favoriteToyId") + ) } public func makeRow() throws -> Row { @@ -37,6 +44,7 @@ final class Pet: Entity { try row.set(Pet.idKey, self.id) try row.set("name", self.name) try row.set("age", self.age) + try row.set("favoriteToyId", self.favoriteToyId) return row } @@ -46,6 +54,10 @@ final class Pet: Entity { extension Pet { + public var favoriteToy: Parent { + return self.parent(id: self.favoriteToyId) + } + public var toys: Siblings> { return self.siblings() }