-
-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #232 from OpenKitten/feature/findAndModify
Initial implementation of findAndModify.
- Loading branch information
Showing
2 changed files
with
283 additions
and
7 deletions.
There are no files selected for viewing
169 changes: 169 additions & 0 deletions
169
Sources/MongoKitten/CollectionHelpers/Collection+FindAndModify.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import NIO | ||
import MongoClient | ||
import MongoKittenCore | ||
|
||
extension MongoCollection { | ||
// MARK: - Builder Functions (Composable/Chained API) | ||
|
||
/// Modifies and returns a single document. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the modification. | ||
/// - update: If passed a document with update operator expressions, performs the specified modification. If passed a replacement document performs a replacement. | ||
/// - remove: Removes the document specified in the query field. Defaults to `false` | ||
/// - returnValue: Wether to return the `original` or `modified` document. | ||
public func findAndModify(where query: Document, | ||
update document: Document = [:], | ||
remove: Bool = false, | ||
returnValue: FindAndModifyReturnValue = .original) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query) | ||
command.update = document | ||
command.remove = remove | ||
command.new = returnValue == .modified | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Deletes a single document based on the query, returning the deleted document. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the deletion. | ||
public func findOneAndDelete(where query: Document) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query) | ||
command.remove = true | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Replaces a single document based on the specified query. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the upate. | ||
/// - replacement: The replacement document. | ||
/// - returnValue: Wether to return the `original` or `modified` document. | ||
public func findOneAndReplace(where query: Document, | ||
replacement document: Document, | ||
returnValue: FindAndModifyReturnValue = .original) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query) | ||
command.new = returnValue == .modified | ||
command.update = document | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Updates a single document based on the specified query. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the upate. | ||
/// - document: The update document. | ||
/// - returnValue: Wether to return the `original` or `modified` document. | ||
public func findOneAndUpdate(where query: Document, | ||
to document: Document, | ||
returnValue: FindAndModifyReturnValue = .original) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query) | ||
command.new = returnValue == .modified | ||
command.update = document | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Modifies and returns a single document. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the modification. | ||
/// - update: If passed a document with update operator expressions, performs the specified modification. If passed a replacement document performs a replacement. | ||
/// - remove: Removes the document specified in the query field. Defaults to `false` | ||
/// - returnValue: Wether to return the `original` or `modified` document. | ||
public func findAndModify<Query: MongoKittenQuery>(where query: Query, | ||
update document: Document = [:], | ||
remove: Bool = false, | ||
returnValue: FindAndModifyReturnValue = .original) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query.makeDocument()) | ||
command.update = document | ||
command.remove = remove | ||
command.new = returnValue == .modified | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Deletes a single document based on the query, returning the deleted document. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the deletion. | ||
public func findOneAndDelete<Query: MongoKittenQuery>(where query: Query) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query.makeDocument()) | ||
command.remove = true | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Replaces a single document based on the specified query. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the upate. | ||
/// - replacement: The replacement document. | ||
/// - returnValue: Wether to return the `original` or `modified` document. | ||
public func findOneAndReplace<Query: MongoKittenQuery>(where query: Query, | ||
replacement document: Document, | ||
returnValue: FindAndModifyReturnValue = .original) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query.makeDocument()) | ||
command.new = returnValue == .modified | ||
command.update = document | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
|
||
/// Updates a single document based on the specified query. | ||
/// - Parameters: | ||
/// - query: The selection criteria for the upate. | ||
/// - document: The update document. | ||
/// - returnValue: Wether to return the `original` or `modified` document. | ||
public func findOneAndUpdate<Query: MongoKittenQuery>(where query: Query, | ||
to document: Document, | ||
returnValue: FindAndModifyReturnValue = .original) -> FindAndModifyBuilder { | ||
var command = FindAndModifyCommand(collection: self.name, query: query.makeDocument()) | ||
command.new = returnValue == .modified | ||
command.update = document | ||
return FindAndModifyBuilder(command: command, collection: self) | ||
} | ||
} | ||
|
||
public final class FindAndModifyBuilder { | ||
/// The underlying command to be executed. | ||
public var command: FindAndModifyCommand | ||
private let collection: MongoCollection | ||
|
||
init(command: FindAndModifyCommand, collection: MongoCollection) { | ||
self.command = command | ||
self.collection = collection | ||
} | ||
|
||
/// Executes the command | ||
public func execute() -> EventLoopFuture<FindAndModifyReply> { | ||
return collection.pool.next(for: .basic).flatMap { connection in | ||
connection.executeCodable(self.command, | ||
namespace: self.collection.database.commandNamespace, | ||
in: self.collection.transaction, | ||
sessionId: self.collection.sessionId ?? connection.implicitSessionId) | ||
|
||
} | ||
.decode(FindAndModifyReply.self) | ||
._mongoHop(to: self.collection.hoppedEventLoop) | ||
} | ||
|
||
public func sort(_ sort: Sort) -> FindAndModifyBuilder { | ||
self.command.sort = sort.document | ||
return self | ||
} | ||
|
||
public func sort(_ sort: Document) -> FindAndModifyBuilder { | ||
self.command.sort = sort | ||
return self | ||
} | ||
|
||
public func project(_ projection: Projection) -> FindAndModifyBuilder { | ||
self.command.fields = projection.document | ||
return self | ||
} | ||
|
||
public func project(_ projection: Document) -> FindAndModifyBuilder { | ||
self.command.fields = projection | ||
return self | ||
} | ||
|
||
public func writeConcern(_ concern: WriteConcern) -> FindAndModifyBuilder { | ||
self.command.writeConcern = concern | ||
return self | ||
} | ||
|
||
public func collation(_ collation: Collation) -> FindAndModifyBuilder { | ||
self.command.collation = collation | ||
return self | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,115 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Joannis Orlandos on 21/06/2019. | ||
// | ||
import MongoCore | ||
|
||
import Foundation | ||
public struct FindAndModifyCommand: Codable { | ||
/// The collection against which to run the command. | ||
public private(set) var findAndModify: String | ||
/// The selection criteria for the modification. | ||
public var query: Document? | ||
/// Determines which document the operation modifies if the query selects multiple documents. `findAndModify` modifies the first document in the sort order specified by this argument. | ||
public var sort: Document? | ||
/// Removes the document specified in the `query` field. Set this to `true` to remove the selected document . The default is `false`. | ||
public var remove: Bool | ||
/** | ||
Performs an update of the selected document. | ||
|
||
* If passed a document with update operator expressions, `findAndModify` performs the specified modification. | ||
* If passed a replacement document `{ <field1>: <value1>, ...}`, the `findAndModify` performs a replacement. | ||
* Starting in MongoDB 4.2, if passed an aggregation pipeline `[ <stage1>, <stage2>, ... ]`, `findAndModify` modifies the document per the pipeline. The pipeline can consist of the following stages: | ||
* `$addFields` and its alias `$set` | ||
* `$project` and its alias `$unset` | ||
* `$replaceRoot` and its alias `$replcaeWith` | ||
*/ | ||
public var update: Document = [] | ||
/// When true, returns the modified document rather than the original. The findAndModify method ignores the new option for remove operations. | ||
public var new: Bool? | ||
/// A subset of fields to return. The `fields` document specifies an inclusion of a field with `1`, as in: `fields: { <field1>: 1, <field2>: 1, ... }`. [See projection](https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/#read-operations-projection). | ||
public var fields: Document? | ||
/** | ||
Used in conjuction with the update field. | ||
|
||
When true, `findAndModify()` either: | ||
|
||
* Creates a new document if no documents match the `query`. For more details see [upsert behavior](https://docs.mongodb.com/manual/reference/method/db.collection.update/#upsert-behavior). | ||
* Updates a single document that matches `query`. | ||
|
||
To avoid multiple upserts, ensure that the query fields are uniquely indexed. | ||
*/ | ||
public var upsert: Bool? | ||
/// Enables findAndModify to bypass document validation during the operation. This lets you update documents that do not meet the validation requirements. | ||
public var bypassDocumentValidation: Bool? | ||
/** | ||
A document expressing the write concern. Omit to use the default write concern. | ||
|
||
Do not explicitly set the write concern for the operation if run in a transaction. To use write concern with transactions, see [Transactions and Write Concern](https://docs.mongodb.com/manual/core/transactions/#transactions-write-concern). | ||
*/ | ||
public var writeConcern: WriteConcern? | ||
/// Specifies a time limit in milliseconds for processing the operation. | ||
public var maxTimeMS: Int? | ||
/// Specifies the collation to use for the operation. | ||
public var collation: Collation? | ||
/// An array of filter documents that determine which array elements to modify for an update operation on an array field. | ||
public var arrayFilters: [Document]? | ||
|
||
public init(collection: String, | ||
query: Document? = nil, | ||
sort: Document? = nil, | ||
remove: Bool = false, | ||
update: Document = [], | ||
new: Bool? = nil, | ||
fields: Document? = nil, | ||
upsert: Bool? = nil, | ||
bypassDocumentValidation: Bool? = nil, | ||
writeConcern: WriteConcern? = nil, | ||
maxTimeMS: Int? = nil, | ||
collation: Collation? = nil, | ||
arrayFilters: [Document]? = nil) { | ||
self.findAndModify = collection | ||
self.query = query | ||
self.sort = sort | ||
self.remove = remove | ||
self.update = update | ||
self.new = new | ||
self.fields = fields | ||
self.upsert = upsert | ||
self.bypassDocumentValidation = bypassDocumentValidation | ||
self.writeConcern = writeConcern | ||
self.maxTimeMS = maxTimeMS | ||
self.collation = collation | ||
self.arrayFilters = arrayFilters | ||
} | ||
} | ||
|
||
public struct FindAndModifyReply: Codable, Error { | ||
private enum CodingKeys: String, CodingKey { | ||
case ok | ||
case value | ||
case lastErrorObject | ||
} | ||
|
||
/// Contains the command’s execution status. `1` on success, or `0` if an error occurred. | ||
public let ok: Int | ||
/** | ||
Contains the command’s returned value. | ||
|
||
For `remove` operations, `value` contains the removed document if the query matches a document. If the query does not match a document to remove, `value` contains `nil`. | ||
For update operations, the value embedded document contains the following: | ||
* If the `new` parameter is not set or is `false`: | ||
* the pre-modification document if the query matches a document; | ||
* otherwise, `nil`. | ||
|
||
* if `new` is `true`: | ||
* the modified document if the query returns a match; | ||
* the inserted document if `upsert: true` and no document matches the query; | ||
* otherwise, `nil`. | ||
*/ | ||
public let value: Document? | ||
/// Contains information about updated documents. | ||
public let lastErrorObject: Document? | ||
} | ||
|
||
public enum FindAndModifyReturnValue: String, Codable { | ||
/// Return the modified Document. | ||
case modified | ||
/// Return the original Document. | ||
case original | ||
} |