Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 12 additions & 50 deletions Sources/MongoSwift/MongoCollection+Read.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,50 +79,27 @@ extension SyncMongoCollection {
}
}

// TODO: SWIFT-133: mark this method deprecated https://jira.mongodb.org/browse/SWIFT-133
/**
* Counts the number of documents in this collection matching the provided filter.
* Counts the number of documents in this collection matching the provided filter. Note that an empty filter will
* force a scan of the entire collection. For a fast count of the total documents in a collection see
* `estimatedDocumentCount`.
*
* - Parameters:
* - filter: a `Document`, the filter that documents must match in order to be counted
* - options: Optional `CountOptions` to use when executing the command
* - options: Optional `CountDocumentsOptions` to use when executing the command
* - session: Optional `SyncClientSession` to use when executing this command
*
* - Returns: The count of the documents that matched the filter
*
* - Throws:
* - `ServerError.commandError` if an error occurs that prevents the command from performing the write.
* - `UserError.invalidArgumentError` if the options passed in form an invalid combination.
* - `EncodingError` if an error occurs while encoding the options to BSON.
*/
public func count(
public func countDocuments(
_ filter: Document = [:],
options: CountOptions? = nil,
options: CountDocumentsOptions? = nil,
session: SyncClientSession? = nil
) throws -> Int {
let operation = CountOperation(collection: self, filter: filter, options: options)
let operation = CountDocumentsOperation(collection: self, filter: filter, options: options)
return try self._client.executeOperation(operation, session: session)
}

/**
* Counts the number of documents in this collection matching the provided filter.
*
* - Parameters:
* - filter: a `Document`, the filter that documents must match in order to be counted
* - options: Optional `CountDocumentsOptions` to use when executing the command
* - session: Optional `SyncClientSession` to use when executing this command
*
* - Returns: The count of the documents that matched the filter
*/
private func countDocuments(
_: Document = [:],
options _: CountDocumentsOptions? = nil,
session _: SyncClientSession? = nil
) throws -> Int {
// TODO: SWIFT-133: implement this https://jira.mongodb.org/browse/SWIFT-133
throw UserError.logicError(message: "Unimplemented command")
}

/**
* Gets an estimate of the count of documents in this collection using collection metadata.
*
Expand All @@ -132,12 +109,12 @@ extension SyncMongoCollection {
*
* - Returns: an estimate of the count of documents in this collection
*/
private func estimatedDocumentCount(
options _: EstimatedDocumentCountOptions? = nil,
session _: SyncClientSession? = nil
public func estimatedDocumentCount(
options: EstimatedDocumentCountOptions? = nil,
session: SyncClientSession? = nil
) throws -> Int {
// TODO: SWIFT-133: implement this https://jira.mongodb.org/browse/SWIFT-133
throw UserError.logicError(message: "Unimplemented command")
let operation = EstimatedDocumentCountOperation(collection: self, options: options)
return try self._client.executeOperation(operation, session: session)
}

/**
Expand Down Expand Up @@ -263,21 +240,6 @@ public struct AggregateOptions: Codable {
}
}

/// The `countDocuments` command takes the same options as the deprecated `count`.
private typealias CountDocumentsOptions = CountOptions

/// Options to use when executing an `estimatedDocumentCount` command on a `MongoCollection` or a
/// `SyncMongoCollection`.
private struct EstimatedDocumentCountOptions {
/// The maximum amount of time to allow the query to run.
public let maxTimeMS: Int64?

/// Initializer allowing any/all parameters to be omitted or optional.
public init(maxTimeMS: Int64? = nil) {
self.maxTimeMS = maxTimeMS
}
}

/// The possible types of `MongoCursor` or `SyncMongoCursor` an operation can return.
public enum CursorType {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import mongoc

/// Options to use when executing a `count` command on a `MongoCollection` or a `SyncMongoCollection`.
public struct CountOptions: Codable {
/// Options to use when executing a `countDocuments` command on a `MongoCollection`.
public struct CountDocumentsOptions: Codable {
/// Specifies a collation.
public var collation: Document?

Expand Down Expand Up @@ -50,12 +50,12 @@ public struct CountOptions: Codable {
}

/// An operation corresponding to a "count" command on a collection.
internal struct CountOperation<T: Codable>: Operation {
internal struct CountDocumentsOperation<T: Codable>: Operation {
private let collection: SyncMongoCollection<T>
private let filter: Document
private let options: CountOptions?
private let options: CountDocumentsOptions?

internal init(collection: SyncMongoCollection<T>, filter: Document, options: CountOptions?) {
internal init(collection: SyncMongoCollection<T>, filter: Document, options: CountDocumentsOptions?) {
self.collection = collection
self.filter = filter
self.options = options
Expand All @@ -66,18 +66,7 @@ internal struct CountOperation<T: Codable>: Operation {
let rp = self.options?.readPreference?._readPreference
var error = bson_error_t()
let count = self.collection.withMongocCollection(from: connection) { collPtr in
// because we already encode skip and limit in the options,
// pass in 0s so we don't get duplicate parameter errors.
mongoc_collection_count_with_opts(
collPtr,
MONGOC_QUERY_NONE,
self.filter._bson,
0, // skip
0, // limit
opts?._bson,
rp,
&error
)
mongoc_collection_count_documents(collPtr, self.filter._bson, opts?._bson, rp, nil, &error)
}

guard count != -1 else { throw extractMongoError(error: error) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import mongoc

/// Options to use when executing an `estimatedDocumentCount` command on a `MongoCollection`.
public struct EstimatedDocumentCountOptions: Codable {
/// The maximum amount of time to allow the query to run.
public var maxTimeMS: Int64?

/// A ReadConcern to use for this operation.
public var readConcern: ReadConcern?

// swiftlint:disable redundant_optional_initialization
/// A ReadPreference to use for this operation.
public var readPreference: ReadPreference? = nil
// swiftlint:enable redundant_optional_initialization

/// Convenience initializer allowing any/all parameters to be optional
public init(
maxTimeMS: Int64? = nil,
readConcern: ReadConcern? = nil,
readPreference: ReadPreference? = nil
) {
self.maxTimeMS = maxTimeMS
self.readConcern = readConcern
self.readPreference = readPreference
}

private enum CodingKeys: String, CodingKey {
case maxTimeMS, readConcern
}
}

/// An operation corresponding to a "count" command on a collection.
internal struct EstimatedDocumentCountOperation<T: Codable>: Operation {
private let collection: SyncMongoCollection<T>
private let options: EstimatedDocumentCountOptions?

internal init(collection: SyncMongoCollection<T>, options: EstimatedDocumentCountOptions?) {
self.collection = collection
self.options = options
}

internal func execute(using connection: Connection, session: SyncClientSession?) throws -> Int {
let opts = try encodeOptions(options: options, session: session)
let rp = self.options?.readPreference?._readPreference
var error = bson_error_t()
let count = self.collection.withMongocCollection(from: connection) { collPtr in
mongoc_collection_estimated_document_count(collPtr, opts?._bson, rp, nil, &error)
}

guard count != -1 else { throw extractMongoError(error: error) }

return Int(count)
}
}
3 changes: 2 additions & 1 deletion Tests/MongoSwiftTests/ClientSessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ final class ClientSessionTests: MongoSwiftTestCase {
(name: "find", body: { _ = try $0.find([:], session: $1).nextOrError() }),
(name: "aggregate", body: { _ = try $0.aggregate([], session: $1).nextOrError() }),
(name: "distinct", body: { _ = try $0.distinct(fieldName: "x", session: $1) }),
(name: "count", body: { _ = try $0.count(session: $1) })
(name: "countDocuments", body: { _ = try $0.countDocuments(session: $1) }),
(name: "estimatedDocumentCount", body: { _ = try $0.estimatedDocumentCount(session: $1) })
]

// list of write operations on SyncMongoCollection that take in a session
Expand Down
2 changes: 1 addition & 1 deletion Tests/MongoSwiftTests/CodecTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ final class CodecTests: MongoSwiftTestCase {
"writeConcern"
]))

let count = CountOptions(
let count = CountDocumentsOptions(
collation: Document(),
hint: .indexName("hint"),
limit: 123,
Expand Down
7 changes: 5 additions & 2 deletions Tests/MongoSwiftTests/CommandMonitoringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ final class CommandMonitoringTests: MongoSwiftTestCase {
// read in the file data and parse into a struct
let name = filename.components(separatedBy: ".")[0]

// remove this if/when bulkwrite is supported
// TODO: SWIFT-346: remove this skip
if name.lowercased().contains("bulkwrite") { continue }

// remove this when command.json is updated with the new count API (see SPEC-1272)
if name.lowercased() == "command" { continue }

print("-----------------------")
print("Executing tests for file \(name)...\n")

Expand Down Expand Up @@ -149,7 +152,7 @@ private struct CMTest: Decodable {

switch self.op.name {
case "count":
_ = try? collection.count(filter)
_ = try? collection.countDocuments(filter)
case "deleteMany":
_ = try? collection.deleteMany(filter)
case "deleteOne":
Expand Down
44 changes: 38 additions & 6 deletions Tests/MongoSwiftTests/CrudTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ final class CrudTests: MongoSwiftTestCase {
print("\n------------\nExecuting tests from file \(dir)/\(filename)...\n")

// For each file, execute the test cases contained in it
for (i, test) in file.tests.enumerated() {
for (i, test) in try file.makeTests().enumerated() {
if type(of: test) == CountTest.self {
print("Skipping test for old count API, no longer supported by the driver")
}

print("Executing test: \(test.description)")

// for each test case:
Expand All @@ -40,10 +44,18 @@ final class CrudTests: MongoSwiftTestCase {
// 4) verify that expected data is present
// 5) drop the collection to clean up
let collection = db.collection(self.getCollectionName(suffix: "\(filename)_\(i)"))
try collection.insertMany(file.data)
if !file.data.isEmpty {
try collection.insertMany(file.data)
}
try test.execute(usingCollection: collection)
try test.verifyData(testCollection: collection, db: db)
try collection.drop()
do {
try collection.drop()
} catch let ServerError.commandError(code, _, _, _) where code == 26 {
// ignore ns not found errors
} catch {
throw error
}
}
}
print() // for readability of results
Expand All @@ -64,7 +76,11 @@ final class CrudTests: MongoSwiftTestCase {
private struct CrudTestFile: Decodable {
let data: [Document]
let testDocs: [Document]
var tests: [CrudTest] { return try! self.testDocs.map { try makeCrudTest($0) } }

func makeTests() throws -> [CrudTest] {
return try self.testDocs.map { try makeCrudTest($0) }
}

let minServerVersion: String?
let maxServerVersion: String?

Expand All @@ -88,9 +104,11 @@ private var testTypeMap: [String: CrudTest.Type] = [
"aggregate": AggregateTest.self,
"bulkWrite": BulkWriteTest.self,
"count": CountTest.self,
"countDocuments": CountDocumentsTest.self,
"deleteMany": DeleteTest.self,
"deleteOne": DeleteTest.self,
"distinct": DistinctTest.self,
"estimatedDocumentCount": EstimatedDocumentCountTest.self,
"find": FindTest.self,
"findOneAndDelete": FindOneAndDeleteTest.self,
"findOneAndUpdate": FindOneAndUpdateTest.self,
Expand Down Expand Up @@ -270,10 +288,24 @@ private class BulkWriteTest: CrudTest {

/// A class for executing `count` tests
private class CountTest: CrudTest {
override func execute(usingCollection _: SyncMongoCollection<Document>) throws {}
}

/// A class for executing `countDocuments` tests
private class CountDocumentsTest: CrudTest {
override func execute(usingCollection coll: SyncMongoCollection<Document>) throws {
let filter: Document = try self.args.get("filter")
let options = CountOptions(collation: self.collation, limit: self.limit, skip: self.skip)
let result = try coll.count(filter, options: options)
let options = CountDocumentsOptions(collation: self.collation, limit: self.limit, skip: self.skip)
let result = try coll.countDocuments(filter, options: options)
expect(result).to(equal(self.result?.asInt()))
}
}

/// A class for executing `estimatedDocumentCount` tests
private class EstimatedDocumentCountTest: CrudTest {
override func execute(usingCollection coll: SyncMongoCollection<Document>) throws {
let options = EstimatedDocumentCountOptions()
let result = try coll.estimatedDocumentCount(options: options)
expect(result).to(equal(self.result?.asInt()))
}
}
Expand Down
Loading