Skip to content

Commit

Permalink
RawComputedFields and distinct (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
valeriomazzeo authored and vzsg committed Oct 26, 2018
1 parent 13aa3d7 commit c6582b2
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 12 deletions.
62 changes: 54 additions & 8 deletions Sources/MongoDriver/MongoDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,25 @@ extension MongoKitten.Database : Fluent.Driver, Connection {

return document
}


private func makeRawComputedProperties<E>(_ query: Fluent.Query<E>) throws -> [String] {

guard case .fetch(let computedProperties) = query.action else {
return []
}

return try computedProperties.reduce([String](), { result, property in
switch property {
case .raw(let value, _):
var mutableResult = result
mutableResult += value
return mutableResult
case .some:
throw Error.unsupported
}
})
}

/// Exequtes a Fluent.Query for an Entity
///
///
Expand Down Expand Up @@ -249,9 +267,24 @@ extension MongoKitten.Database : Fluent.Driver, Connection {
let filter = try makeQuery(query.filters, method: .and)
let sort = makeSort(query.sorts)
let (limit, skip) = try makeLimits(query.limits)
let rawComputedProperties = try makeRawComputedProperties(query)

let projectionFields: [String: Projection.ProjectionExpression]

if !rawComputedProperties.isEmpty {
var fields: [String: BSON.Primitive] = rawComputedProperties.reduce([:], { result, property in
var mutableResult = result
mutableResult[property] = "$$ROOT." + property
return mutableResult
})
fields["_id"] = "$$ROOT._id"
projectionFields = [E.name: .custom(fields)]
} else {
projectionFields = [E.name: "$$ROOT"]
}

var pipeline: AggregationPipeline = [
.project([E.name: "$$ROOT"]),
.project(Projection(projectionFields)),
.addFields(["_id": "$" + E.name + "._id"])
]

Expand All @@ -269,6 +302,18 @@ extension MongoKitten.Database : Fluent.Driver, Connection {

pipeline.append(.match(filter))

if query.isDistinct {
let groupId: ExpressionRepresentable = rawComputedProperties.isEmpty
? "null"
: Document(rawComputedProperties.reduce([:], { result, value in
var mutableResult = result
mutableResult[value] = "$\(E.name).\(value)"
return mutableResult
}))
pipeline.append(.group(groupId, computed: [E.name: .first("$\(E.name)")]))
pipeline.append(.project(Projection(["_id": "$\(E.name)._id", "\(E.name)": "$\(E.name)"])))
}

if let sort = sort {
pipeline.append(.sort(sort))
}
Expand All @@ -286,15 +331,10 @@ extension MongoKitten.Database : Fluent.Driver, Connection {

private func fetch<E>(_ query: Fluent.Query<E>) throws -> Node {

guard case .fetch(let computedProperties) = query.action else {
guard case .fetch(_) = query.action else {
throw Error.invalidQuery
}

// TODO: Support ComputedProperties
guard computedProperties.isEmpty else {
throw Error.unsupported
}

let collection = self[E.entity]
let pipeline = try makeAggregationPipeline(query)

Expand Down Expand Up @@ -421,3 +461,9 @@ extension MongoKitten.Database : Fluent.Driver, Connection {
}
}
}

extension Projection {
init(_ dictionary: [String: Projection.ProjectionExpression]) {
self.init(Document(dictionaryElements: dictionary.map { ($0.0, $0.1) }))
}
}
65 changes: 63 additions & 2 deletions Tests/MongoDriverTests/DriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class DriverTests: XCTestCase {
("testInsertAndFind", testInsertAndFind),
("testArray", testArray),
("testArrayOfArrays", testArrayOfArrays),
("testDistinct", testDistinct),
("testDistinctWithoutRawComputedFields", testDistinctWithoutRawComputedFields),
("testOuterJoin", testOuterJoin),
("testSiblingsCount", testSiblingsCount),
("testMax", testMax),
Expand Down Expand Up @@ -90,6 +92,65 @@ class DriverTests: XCTestCase {
XCTAssertEqual(foundNode.node["key2"]!.array!.last!.array!.flatMap { $0.string }, ["k2i1v1", "k2i1v2"])
}

func testDistinct() throws {
try driver.drop()
let db = Fluent.Database(driver)

Toy.database = db

let ball = Toy(name: "ball", material: "rubber")
let bone = Toy(name: "bone", material: "plastic")
let puppet = Toy(name: "puppet")
let anotherBall = Toy(name: "ball", material: "rubber")

try ball.save()
try bone.save()
try puppet.save()
try anotherBall.save()

let toys = try Toy.makeQuery().distinct().all([.raw("name", [])])

XCTAssertEqual(toys.count, 3)
XCTAssertTrue(toys.contains(where: { $0.name == "ball" }))
XCTAssertTrue(toys.contains(where: { $0.name == "bone" }))
XCTAssertTrue(toys.contains(where: { $0.name == "puppet" }))
}

func testDistinctWithoutRawComputedFields() throws {
try driver.drop()
let db = Fluent.Database(driver)

Adult.database = db
Child.database = db

let alice = Adult(name: "Alice")
let bob = Adult(name: "Bob")
let charlie = Adult(name: "Charlie")

XCTAssertNoThrow(try alice.save())
XCTAssertNoThrow(try bob.save())
XCTAssertNoThrow(try charlie.save())

let molly = Child(name: "Molly", age: 2, parentId: try alice.assertExists())
let kevin = Child(name: "Rex", age: 1, parentId: try bob.assertExists())
let bill = Child(name: "Sparky", age: 1, parentId: try bob.assertExists())

XCTAssertNoThrow(try molly.save())
XCTAssertNoThrow(try kevin.save())
XCTAssertNoThrow(try bill.save())

let adults = try Adult
.makeQuery()
.join(kind: .inner, Child.self, baseKey: Adult.idKey, joinedKey: "parentId")
.filter(Child.self, "age", 1)
.distinct()
.all()

XCTAssertEqual(adults.count, 1)
XCTAssertEqual(adults.first?.id, try bob.assertExists())
XCTAssertEqual(adults.first?.name, "Bob")
}

func testOuterJoin() throws {
try driver.drop()
let db = Fluent.Database(driver)
Expand Down Expand Up @@ -118,15 +179,15 @@ class DriverTests: XCTestCase {

let toysFavoritedByPets = try Toy.makeQuery()
.join(kind: .inner, Pet.self, baseKey: Toy.idKey, joinedKey: "favoriteToyId")
.all()
.all([.raw("name", [])])

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()
.all([.raw("name", [])])

XCTAssertEqual(toysNotFavoritedByPets.count, 2)
XCTAssertTrue(toysNotFavoritedByPets.contains(where: { $0.id == bone.id }))
Expand Down
83 changes: 83 additions & 0 deletions Tests/MongoDriverTests/People.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// People.swift
// MongoDriverTests
//
// Created by Valerio Mazzeo on 25/10/2018.
//

import Foundation
import Fluent

final class Adult: Entity {

public let name: String

public init(name: String) {
self.name = name
}

// MARK: Storable

public let storage = Storage()

// MARK: RowConvertible

public convenience init(row: Row) throws {

self.init(
name: try row.get("name")
)
}

public func makeRow() throws -> Row {

var row = Row()

try row.set(Adult.idKey, self.id)
try row.set("name", self.name)

return row
}
}

final class Child: Entity {

public let name: String

public var age: Int

public var parentId: Identifier

public init(name: String, age: Int, parentId: Identifier) {
self.name = name
self.age = age
self.parentId = parentId
}

// MARK: Storable

public let storage = Storage()

// MARK: RowConvertible

public convenience init(row: Row) throws {

self.init(
name: try row.get("name"),
age: try row.get("age"),
parentId: try row.get("parentId")
)
}

public func makeRow() throws -> Row {

var row = Row()

try row.set(Child.idKey, self.id)
try row.set("name", self.name)
try row.set("age", self.age)
try row.set("parentId", self.parentId)

return row
}
}
14 changes: 12 additions & 2 deletions Tests/MongoDriverTests/Toy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ final class Toy: Entity {

public let name: String

public init(name: String) {
public let material: String?

public init(name: String, material: String? = nil) {
self.name = name
self.material = material
}

// MARK: Storable
Expand All @@ -24,7 +27,10 @@ final class Toy: Entity {

public convenience init(row: Row) throws {

self.init(name: try row.get("name"))
self.init(
name: try row.get("name"),
material: try? row.get("material")
)
}

public func makeRow() throws -> Row {
Expand All @@ -34,6 +40,10 @@ final class Toy: Entity {
try row.set(Toy.idKey, self.id)
try row.set("name", self.name)

if let material = self.material {
try row.set("material", material)
}

return row
}
}
Expand Down

0 comments on commit c6582b2

Please sign in to comment.