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
28 changes: 15 additions & 13 deletions Guides/Development.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Swift Driver Development Guide

## Index
* [Things to Install](#things-to-install)
* [Things to Install](#things-to-install)
* [The Code](#the-code)
* [Building](#building)
* [Running Tests](#running-tests)
Expand All @@ -15,15 +15,17 @@
* [swiftenv](https://swiftenv.fuller.li/en/latest/installation.html): a command-line tool that allows easy installation of and switching between versions of Swift.
* [Jazzy](https://github.com/realm/jazzy#installation): the tool we use to generate documentation.
* [SwiftFormat](https://github.com/nicklockwood/SwiftFormat#how-do-i-install-it): the Swift formatter we use.
* [SwiftLint](https://github.com/realm/SwiftLint#using-homebrew): the Swift linter we use.
* [SwiftLint](https://github.com/realm/SwiftLint#using-homebrew): the Swift linter we use.
* [Sourcery](https://github.com/krzysztofzablocki/Sourcery/#installation): the tool we use for code generation.

## The code
You should clone this repository, as well as the [MongoDB Driver specifications](https://github.com/mongodb/specifications).

## Building
## Building
### From the Command line
Run `swift build` or simply `make` in the project's root directory.
Run `swift build` or simply `make` in the project's root directory.

If you add symbols you may need to run `make exports` which will generate [Sources/MongoSwiftSync/Exports.swift](Sources/MongoSwiftSync/Exports.swift). This makes symbols declared in `MongoSwift` available to importers of `MongoSwiftSync`.

### In Xcode
We do not provide or maintain an already-generated `.xcodeproj` in our repository. Instead, you must generate it locally.
Expand All @@ -33,21 +35,21 @@ We do not provide or maintain an already-generated `.xcodeproj` in our repositor
2. Run `make project`
3. You're ready to go! Open `MongoSwift.xcodeproj` and build and test as normal.

Why is this necessary? The project requires a customized "copy resources" build phase to include various test `.json` files. By default, this phase is not included when you run `swift package generate-xcodeproj`. So `make project` first generates the project, and then uses `xcodeproj` to manually add the files to the appropriate targets (see `add_json_files.rb`).
Why is this necessary? The project requires a customized "copy resources" build phase to include various test `.json` files. By default, this phase is not included when you run `swift package generate-xcodeproj`. So `make project` first generates the project, and then uses `xcodeproj` to manually add the files to the appropriate targets (see `add_json_files.rb`).

## Running Tests
**NOTE**: Several of the tests require a mongod instance to be running on the default host/port, `localhost:27017`. You can start this by running `mongod --setParameter enableTestCommands=1`. The `enableTestCommands` parameter is required to use some test-only commands built into MongoDB that we utilize in our tests, e.g. `failCommand`.

You can run tests from Xcode as usual. If you prefer to test from the command line, keep reading.

### From the Command Line
### From the Command Line
We recommend installing the ruby gem `xcpretty` and running tests by executing `make test-pretty`, as this provides output in a much more readable format. (Works on MacOS only.)

Alternatively, you can just run the tests with `swift test`, or `make test`.

To filter tests by regular expression:
- If you are using `swift test`, provide the `--filter` argument: for example, `swift test --filter=MongoClientTests`.
- If you are using `make test` or `make test-pretty`, provide the `FILTER` environment variable: for example, `make test-pretty FILTER=MongoCollectionTests`.
- If you are using `swift test`, provide the `--filter` argument: for example, `swift test --filter=MongoClientTests`.
- If you are using `make test` or `make test-pretty`, provide the `FILTER` environment variable: for example, `make test-pretty FILTER=MongoCollectionTests`.

### Diagnosing Backtraces on Linux

Expand All @@ -61,10 +63,10 @@ $ symbolicate-linux-fatal /path/to/MongoSwiftPackageTests.xctest crash.log
This will require you to manually provide the path to the compiled test binary (e.g. `.build/x86_64-unknown-linux/debug/MongoSwiftPackageTests.xctest`).

## Writing and Generating Documentation
We document new code as we write it. We use C-style documentation blocks (`/** ... */`) for documentation longer than 3 lines, and triple-slash (`///`) for shorter documentation.
We document new code as we write it. We use C-style documentation blocks (`/** ... */`) for documentation longer than 3 lines, and triple-slash (`///`) for shorter documentation.
Comments that are _not_ documentation should use two slashes (`//`).

Documentation comments should generally be complete sentences and should end with periods.
Documentation comments should generally be complete sentences and should end with periods.

Our documentation site is automatically generated from the source code using [Jazzy](https://github.com/realm/jazzy#installation). We regenerate it via our release script each time we release a new version of the driver.

Expand Down Expand Up @@ -93,7 +95,7 @@ If you have a setup for developing the driver in an editor other than the ones l
* Please read the [NOTICE](https://github.com/Utagai/swift.vim#notice) for proper credits.

## Workflow
1. Create a feature branch, named by the corresponding JIRA ticket if exists, along with a short descriptor of the work: for example, `SWIFT-30/writeconcern`.
1. Create a feature branch, named by the corresponding JIRA ticket if exists, along with a short descriptor of the work: for example, `SWIFT-30/writeconcern`.
1. Do your work on the branch.
1. If you add, remove, or rename any tests, make sure to update `LinuxMain.swift` accordingly. If you are on MacOS, you can do that by running `make linuxmain`.
1. Ensure your code passes both the linter and the formatter.
Expand All @@ -103,8 +105,8 @@ If you have a setup for developing the driver in an editor other than the ones l
1. Open a pull request on the repository. Make sure you have rebased your branch onto the latest commits on `master`.
1. Go through code review to get the team's approval on your changes. (See the next section on [Code Review](#code-review) for more details on this process.) Once you get the required approvals and your code passes all tests:
1. Rebase on master again if needed.
1. Rerun tests.
1. Squash all commits into a single, descriptive commit method, formatted as: `TICKET-NUMBER: Description of changes`. For example, `SWIFT-30: Implement WriteConcern type`.
1. Rerun tests.
1. Squash all commits into a single, descriptive commit method, formatted as: `TICKET-NUMBER: Description of changes`. For example, `SWIFT-30: Implement WriteConcern type`.
1. Merge it, or if you don't have permissions, ask someone to merge it for you.

## Code Review
Expand Down
15 changes: 12 additions & 3 deletions Sources/MongoSwift/MongoClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ public class MongoClient {
* - Parameters:
* - filter: Optional `Document` specifying a filter that the listed databases must pass. This filter can be based
* on the "name", "sizeOnDisk", "empty", or "shards" fields of the output.
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
* - session: Optional `ClientSession` to use when executing this command.
*
* - Returns:
Expand All @@ -352,14 +353,16 @@ public class MongoClient {
* - `LogicError` if the provided session is inactive.
* - `LogicError` if this client has already been closed.
* - `EncodingError` if an error is encountered while encoding the options to BSON.
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
*
* - SeeAlso: https://docs.mongodb.com/manual/reference/command/listDatabases/
*/
public func listDatabases(
_ filter: Document? = nil,
options: ListDatabasesOptions? = nil,
session: ClientSession? = nil
) -> EventLoopFuture<[DatabaseSpecification]> {
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: nil)
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: nil, options: options)
return self.operationExecutor.execute(operation, client: self, session: session).flatMapThrowing { result in
guard case let .specs(dbs) = result else {
throw InternalError(message: "Invalid result")
Expand All @@ -373,6 +376,7 @@ public class MongoClient {
*
* - Parameters:
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
* - session: Optional `ClientSession` to use when executing this command
*
* - Returns:
Expand All @@ -382,19 +386,22 @@ public class MongoClient {
* If the future fails, the error is likely one of the following:
* - `LogicError` if the provided session is inactive.
* - `LogicError` if this client has already been closed.
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
*/
public func listMongoDatabases(
_ filter: Document? = nil,
options: ListDatabasesOptions? = nil,
session: ClientSession? = nil
) -> EventLoopFuture<[MongoDatabase]> {
self.listDatabaseNames(filter, session: session).map { $0.map { self.db($0) } }
self.listDatabaseNames(filter, options: options, session: session).map { $0.map { self.db($0) } }
}

/**
* Get the names of databases in this client's MongoDB deployment.
*
* - Parameters:
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
* - session: Optional `ClientSession` to use when executing this command
*
* - Returns:
Expand All @@ -404,12 +411,14 @@ public class MongoClient {
* If the future fails, the error is likely one of the following:
* - `LogicError` if the provided session is inactive.
* - `LogicError` if this client has already been closed.
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
*/
public func listDatabaseNames(
_ filter: Document? = nil,
options: ListDatabasesOptions? = nil,
session: ClientSession? = nil
) -> EventLoopFuture<[String]> {
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: true)
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: true, options: options)
return self.operationExecutor.execute(operation, client: self, session: session).flatMapThrowing { result in
guard case let .names(names) = result else {
throw InternalError(message: "Invalid result")
Expand Down
19 changes: 18 additions & 1 deletion Sources/MongoSwift/Operations/ListDatabasesOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,34 @@ internal enum ListDatabasesResults {
case names([String])
}

/// Options for "listDatabases" operations.
public struct ListDatabasesOptions {
/// Specifies whether to only return databases for which the user has privileges.
public var authorizedDatabases: Bool?

/// Convenience initializer allowing any/all parameters to be omitted or optional.
public init(authorizedDatabases: Bool? = nil) {
self.authorizedDatabases = authorizedDatabases
}
}

/// An operation corresponding to a "listDatabases" command on a collection.
internal struct ListDatabasesOperation: Operation {
private let client: MongoClient
private let filter: Document?
private let nameOnly: Bool?
private let options: ListDatabasesOptions?

internal init(
client: MongoClient,
filter: Document?,
nameOnly: Bool?
nameOnly: Bool?,
options: ListDatabasesOptions?
) {
self.client = client
self.filter = filter
self.nameOnly = nameOnly
self.options = options
}

internal func execute(using connection: Connection, session: ClientSession?) throws -> ListDatabasesResults {
Expand All @@ -51,6 +65,9 @@ internal struct ListDatabasesOperation: Operation {
if let nameOnly = self.nameOnly {
cmd["nameOnly"] = .bool(nameOnly)
}
if let authorizedDatabases = self.options?.authorizedDatabases {
cmd["authorizedDatabases"] = .bool(authorizedDatabases)
}

let opts = try encodeOptions(options: nil as Document?, session: session)
var reply = Document()
Expand Down
1 change: 1 addition & 0 deletions Sources/MongoSwiftSync/Exports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
@_exported import struct MongoSwift.InternalError
@_exported import struct MongoSwift.InvalidArgumentError
@_exported import struct MongoSwift.ListCollectionsOptions
@_exported import struct MongoSwift.ListDatabasesOptions
@_exported import struct MongoSwift.LogicError
@_exported import struct MongoSwift.MongoNamespace
@_exported import struct MongoSwift.ObjectId
Expand Down
20 changes: 16 additions & 4 deletions Sources/MongoSwiftSync/MongoClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,56 +92,68 @@ public class MongoClient {
* - Parameters:
* - filter: Optional `Document` specifying a filter that the listed databases must pass. This filter can be based
* on the "name", "sizeOnDisk", "empty", or "shards" fields of the output.
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
* - session: Optional `ClientSession` to use when executing this command.
*
* - Returns: A `[DatabaseSpecification]` containing the databases matching provided criteria.
*
* - Throws:
* - `LogicError` if the provided session is inactive.
* - `EncodingError` if an error is encountered while encoding the options to BSON.
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
*
* - SeeAlso: https://docs.mongodb.com/manual/reference/command/listDatabases/
*/
public func listDatabases(
_ filter: Document? = nil,
options: ListDatabasesOptions? = nil,
session: ClientSession? = nil
) throws -> [DatabaseSpecification] {
try self.asyncClient.listDatabases(filter, session: session?.asyncSession).wait()
try self.asyncClient.listDatabases(filter, options: options, session: session?.asyncSession).wait()
}

/**
* Get a list of `MongoDatabase`s.
*
* - Parameters:
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
* - session: Optional `ClientSession` to use when executing this command
*
* - Returns: An Array of `MongoDatabase`s that match the provided filter.
*
* - Throws:
* - `LogicError` if the provided session is inactive.
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
*/
public func listMongoDatabases(
_ filter: Document? = nil,
options: ListDatabasesOptions? = nil,
session: ClientSession? = nil
) throws -> [MongoDatabase] {
try self.listDatabaseNames(filter, session: session).map { self.db($0) }
try self.listDatabaseNames(filter, options: options, session: session).map { self.db($0) }
}

/**
* Get a list of names of databases.
*
* - Parameters:
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
* - session: Optional `ClientSession` to use when executing this command
*
* - Returns: A `[String]` containing names of databases that match the provided filter.
*
* - Throws:
* - `LogicError` if the provided session is inactive.
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
*/
public func listDatabaseNames(_ filter: Document? = nil, session: ClientSession? = nil) throws -> [String] {
try self.asyncClient.listDatabaseNames(filter, session: session?.asyncSession).wait()
public func listDatabaseNames(
_ filter: Document? = nil,
options: ListDatabasesOptions? = nil,
session: ClientSession? = nil
) throws -> [String] {
try self.asyncClient.listDatabaseNames(filter, options: options, session: session?.asyncSession).wait()
}

/**
Expand Down
23 changes: 23 additions & 0 deletions Tests/MongoSwiftSyncTests/SyncMongoClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,29 @@ final class SyncMongoClientTests: MongoSwiftTestCase {
if MongoSwiftTestCase.topologyType == .sharded {
expect(dbInfo.first?.shards).toNot(beNil())
}

let monitor = client.addCommandMonitor()

try monitor.captureEvents {
var opts = ListDatabasesOptions(authorizedDatabases: true)
_ = try client.listDatabaseNames(nil, options: opts, session: nil)
opts.authorizedDatabases = false
_ = try client.listDatabaseNames(nil, options: opts, session: nil)
_ = try client.listDatabaseNames()
}

let events = monitor.commandStartedEvents()
expect(events).to(haveCount(3))

let listDbsAuthTrue = events[0]
expect(listDbsAuthTrue.command["listDatabases"]).toNot(beNil())
expect(listDbsAuthTrue.command["authorizedDatabases"]?.boolValue).to(beTrue())
let listDbsAuthFalse = events[1]
expect(listDbsAuthFalse.command["listDatabases"]).toNot(beNil())
expect(listDbsAuthFalse.command["authorizedDatabases"]?.boolValue).to(beFalse())
let listDbsAuthNil = events[2]
expect(listDbsAuthNil.command["listDatabases"]).toNot(beNil())
expect(listDbsAuthNil.command["authorizedDatabases"]).to(beNil())
}

func testFailedClientInitialization() {
Expand Down