Skip to content

Commit

Permalink
Merge branch 'master/6.0' of github.com:OpenKitten/MongoKitten into m…
Browse files Browse the repository at this point in the history
…aster/6.0
  • Loading branch information
Joannis committed Apr 21, 2020
2 parents a81622a + 6fcd238 commit 6858a97
Show file tree
Hide file tree
Showing 5 changed files with 517 additions and 7 deletions.
32 changes: 32 additions & 0 deletions Sources/MongoKitten/Aggregate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,38 @@ import MongoKittenCore
import MongoClient

extension MongoCollection {
/// The `aggregate` command will create an `AggregateBuilderPipeline` where data can be aggregated
/// and be transformed in multiple `AggregateStage` operations
///
/// # Hint:
/// With Swift > 5.1 you can also use the function builders. See the documentation at `buildAggregate`
///
/// # Example:
/// ```
/// let pipeline = collection.aggregate([
/// .match("name" == "Superman"),
/// .lookup(from: "addresses", "localField": "_id", "foreignField": "superheroID", newName: "address"),
/// .unwind(fieldPath: "$address")
/// ])
///
/// pipeline.decode(SomeDecodableType.self).forEach { yourStruct in
/// // do sth. with your struct
/// }.whenFailure { error in
/// // do sth. with the error
/// }
/// ```
///
/// The same example with function builders:
///
/// ```
/// let pipeline = collection.buildAggregate {
/// match("name" == "Superman")
/// unwind(fieldPath: "$arrayItem")
/// }
/// ```
///
/// - Parameter stages: an array of `AggregateBuilderStage`.
/// - Returns: an `AggregateBuilderPipeline` that should be executed to get results
public func aggregate(_ stages: [AggregateBuilderStage]) -> AggregateBuilderPipeline {
var pipeline = AggregateBuilderPipeline(stages: stages)
pipeline.collection = self
Expand Down
112 changes: 112 additions & 0 deletions Sources/MongoKitten/AggregateBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ public struct AggregateBuilder {
}

extension MongoCollection {
/// The `aggregate` command will create an `AggregateBuilderPipeline` where data can be aggregated
/// and be transformed in multiple `AggregateStage` operations
///
/// With Swift > 5.1 you can use the function builders instead of the `aggregate(_ stages: [AggregateBuilderStage]) -> AggregateBuilderPipeline` function.
///
/// # Example:
/// ```
/// let pipeline = collection.buildAggregate {
/// match("name" == "Superman")
/// lookup(from: "addresses", "localField": "_id", "foreignField": "superheroID", newName: "address")
/// unwind(fieldPath: "$address")
/// }
///
/// pipeline.decode(SomeDecodableType.self).forEach { yourStruct in
/// // do sth. with your struct
/// }.whenFailure { error in
/// // do sth. with the error
/// }
/// ```
///
/// - Parameter build: the `AggregateBuilderStage` as function builders
/// - Returns: an `AggregateBuilderPipeline` that should be executed to get results
public func buildAggregate(@AggregateBuilder build: () -> AggregateBuilderStage) -> AggregateBuilderPipeline {
var pipeline = AggregateBuilderPipeline(stages: [build()])
pipeline.collection = self
Expand All @@ -75,6 +97,25 @@ public func skip(_ n: Int) -> AggregateBuilderStage {
return .skip(n)
}

/// The `limit` aggregation limits the number of resulting documents to the given number
///
/// # MongoDB-Documentation:
/// [Link to the MongoDB-Documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/limit/)
///
/// # Example:
/// ```
/// let pipeline = myCollection.aggregate([
/// .match("myCondition" == true),
/// .limit(5)
/// ])
///
/// pipeline.execute().whenComplete { result in
/// // ...
/// }
/// ```
///
/// - Parameter n: the maximum number of documents
/// - Returns: an `AggregateBuilderStage`
public func limit(_ n: Int) -> AggregateBuilderStage {
return .limit(n)
}
Expand All @@ -97,6 +138,44 @@ public func project(_ fields: String...) -> AggregateBuilderStage {
return .project(projection)
}

/// The `lookup` aggregation performs a join from another collection in the same database. This aggregation will add a new array to
/// your document including the matching documents.
///
/// # MongoDB-Documentation:
/// [Link to the MongoDB-Documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/)
///
/// # Example:
/// There are two collections, named `users` and `userCategories`. In the `users` collection there is a reference to the _id
/// of the `userCategories`, because every user belongs to a category.
///
/// If you now want to aggregate all users and the corresponding user category, you can use the `$lookup` like this:
///
/// ```
/// let pipeline = userCollection.aggregate([
/// .lookup(from: "userCategories", "localField": "categoryID", "foreignField": "_id", newName: "userCategory")
/// ])
///
/// pipeline.execute().whenComplete { result in
/// // ...
/// }
/// ```
///
/// # Hint:
/// Because the matched documents will be inserted as an array no matter if there is only one item or more, you may want to unwind the joined documents:
///
/// ```
/// let pipeline = myCollection.aggregate([
/// .lookup(from: ..., newName: "newName"),
/// .unwind(fieldPath: "$newName")
/// ])
/// ```
///
/// - Parameters:
/// - from: the foreign collection, where the documents will be looked up
/// - localField: the name of the field in the input collection that shall match the `foreignField` in the `from` collection
/// - foreignField: the name of the field in the `fromCollection` that shall match the `localField` in the input collection
/// - newName: the collecting matches will be inserted as an array to the input collection, named as `newName`
/// - Returns: an `AggregateBuilderStage`
public func lookup(
from: String,
localField: String,
Expand All @@ -111,6 +190,39 @@ public func lookup(
)
}

/// The `unwind` aggregation will deconstruct a field, that contains an array. It will return as many documents as are included
/// in the array and every output includes the original document with each item of the array
///
/// # MongoDB-Documentation:
/// [Link to the MongoDB-Documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/)
///
/// # Example:
/// The original document:
///
/// ```
/// { "_id": 1, "boolItem": true, "arrayItem": ["a", "b", "c"] }
/// ```
///
/// The command in Swift:
///
/// ```
/// let pipeline = collection.aggregate([
/// .match("_id" == 1),
/// .unwind(fieldPath: "$arrayItem")
/// ])
/// ```
///
/// This will return three documents:
/// ```
/// { "_id": 1, "boolItem": true, "arrayItem": "a" }
/// { "_id": 1, "boolItem": true, "arrayItem": "b" }
/// { "_id": 1, "boolItem": true, "arrayItem": "c" }
/// ```
/// - Parameters:
/// - fieldPath: the field path to an array field. You have to prefix the path with "$"
/// - includeArrayIndex: this parameter is optional. If given, the new documents will hold a new field with the name of `includeArrayIndex` and this field will contain the array index
/// - preserveNullAndEmptyArrays: this parameter is optional. If it is set to `true`, the aggregation will also include the documents, that don't have an array that can be unwinded. default is `false`, so the `unwind` aggregation will remove all documents, where there is no value or an empty array at `fieldPath`
/// - Returns: an `AggregateBuilderStage`
public func unwind(
fieldPath: String,
includeArrayIndex: String? = nil,
Expand Down
90 changes: 90 additions & 0 deletions Sources/MongoKitten/AggregateStage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ public struct AggregateBuilderStage {
])
}

/// The `limit` aggregation limits the number of resulting documents to the given number
///
/// # MongoDB-Documentation:
/// [Link to the MongoDB-Documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/limit/)
///
/// # Example:
/// ```
/// let pipeline = myCollection.aggregate([
/// .match("myCondition" == true),
/// .limit(5)
/// ])
///
/// pipeline.execute().whenComplete { result in
/// ...
/// }
/// ```
///
/// - Parameter n: the maximum number of documents
/// - Returns: an `AggregateBuilderStage`
public static func limit(_ n: Int) -> AggregateBuilderStage {
assert(n > 0)

Expand All @@ -82,6 +101,44 @@ public struct AggregateBuilderStage {
])
}

/// The `lookup` aggregation performs a join from another collection in the same database. This aggregation will add a new array to
/// your document including the matching documents.
///
/// # MongoDB-Documentation:
/// [Link to the MongoDB-Documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/)
///
/// # Example:
/// There are two collections, named `users` and `userCategories`. In the `users` collection there is a reference to the _id
/// of the `userCategories`, because every user belongs to a category.
///
/// If you now want to aggregate all users and the corresponding user category, you can use the `$lookup` like this:
///
/// ```
/// let pipeline = userCollection.aggregate([
/// .lookup(from: "userCategories", "localField": "categoryID", "foreignField": "_id", newName: "userCategory")
/// ])
///
/// pipeline.execute().whenComplete { result in
/// ...
/// }
/// ```
///
/// # Hint:
/// Because the matched documents will be inserted as an array no matter if there is only one item or more, you may want to unwind the joined documents:
///
/// ```
/// let pipeline = myCollection.aggregate([
/// .lookup(from: ..., newName: "newName"),
/// .unwind(fieldPath: "$newName")
/// ])
/// ```
///
/// - Parameters:
/// - from: the foreign collection, where the documents will be looked up
/// - localField: the name of the field in the input collection that shall match the `foreignField` in the `from` collection
/// - foreignField: the name of the field in the `fromCollection` that shall match the `localField` in the input collection
/// - newName: the collecting matches will be inserted as an array to the input collection, named as `newName`
/// - Returns: an `AggregateBuilderStage`
public static func lookup(
from: String,
localField: String,
Expand All @@ -98,6 +155,39 @@ public struct AggregateBuilderStage {
])
}

/// The `unwind` aggregation will deconstruct a field, that contains an array. It will return as many documents as are included
/// in the array and every output includes the original document with each item of the array
///
/// # MongoDB-Documentation:
/// [Link to the MongoDB-Documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/)
///
/// # Example:
/// The original document:
///
/// ```
/// { "_id": 1, "boolItem": true, "arrayItem": ["a", "b", "c"] }
/// ```
///
/// The command in Swift:
///
/// ```
/// let pipeline = collection.aggregate([
/// .match("_id" == 1),
/// .unwind(fieldPath: "$arrayItem")
/// ])
/// ```
///
/// This will return three documents:
/// ```
/// { "_id": 1, "boolItem": true, "arrayItem": "a" }
/// { "_id": 1, "boolItem": true, "arrayItem": "b" }
/// { "_id": 1, "boolItem": true, "arrayItem": "c" }
/// ```
/// - Parameters:
/// - fieldPath: the field path to an array field. You have to prefix the path with "$"
/// - includeArrayIndex: this parameter is optional. If given, the new documents will hold a new field with the name of `includeArrayIndex` and this field will contain the array index
/// - preserveNullAndEmptyArrays: this parameter is optional. If it is set to `true`, the aggregation will also include the documents, that don't have an array that can be unwinded. default is `false`, so the `unwind` aggregation will remove all documents, where there is no value or an empty array at `fieldPath`
/// - Returns: an `AggregateBuilderStage`
public static func unwind(
fieldPath: String,
includeArrayIndex: String? = nil,
Expand Down
Loading

0 comments on commit 6858a97

Please sign in to comment.