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
71 changes: 52 additions & 19 deletions Sources/MongoSwift/ConnectionString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ internal class ConnectionString {
self.readConcern = rc
}

if let replicaSet = options?.replicaSet {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to move this since some of the validation in applyAndValidateSDAMOptions relies on the value of this. it also seems like this probably should have been in that method anyway since it's an option related to SDAM

try self.setUTF8Option(MONGOC_URI_REPLICASET, to: replicaSet)
}

if let rr = options?.retryReads {
try self.setBoolOption(MONGOC_URI_RETRYREADS, to: rr)
}
Expand Down Expand Up @@ -360,24 +356,15 @@ internal class ConnectionString {

/// Sets and validates SDAM-related on the underlying `mongoc_uri_t`.
private func applyAndValidateSDAMOptions(_ options: MongoClientOptions?) throws {
// First, apply all the options...

if let replicaSet = options?.replicaSet {
try self.setUTF8Option(MONGOC_URI_REPLICASET, to: replicaSet)
}

// Per SDAM spec: If the ``directConnection`` option is not specified, newly developed drivers MUST behave as
// if it was specified with the false value.
if let dc = options?.directConnection {
guard !(dc && self.usesDNSSeedlistFormat) else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_DIRECTCONNECTION)=true is incompatible with mongodb+srv connection strings"
)
}

if let hosts = self.hosts {
guard !(dc && hosts.count > 1) else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_DIRECTCONNECTION)=true is incompatible with multiple seeds. " +
"got seeds: \(hosts)"
)
}
}

try self.setBoolOption(MONGOC_URI_DIRECTCONNECTION, to: dc)
} else if !self.hasOption(MONGOC_URI_DIRECTCONNECTION) {
try self.setBoolOption(MONGOC_URI_DIRECTCONNECTION, to: false)
Expand All @@ -399,6 +386,52 @@ internal class ConnectionString {

try self.setInt32Option(MONGOC_URI_HEARTBEATFREQUENCYMS, to: value)
}

if let loadBalanced = options?.loadBalanced {
try self.setBoolOption(MONGOC_URI_LOADBALANCED, to: loadBalanced)
}

// Then, validate them...
let lb = self.options?[MONGOC_URI_LOADBALANCED]?.boolValue
let dc = self.options?[MONGOC_URI_DIRECTCONNECTION]?.boolValue
let hosts = self.hosts ?? []

if dc == true {
guard !self.usesDNSSeedlistFormat else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_DIRECTCONNECTION)=true is incompatible with mongodb+srv connection strings"
)
}

guard lb != true else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_DIRECTCONNECTION)=true is incompatible with \(MONGOC_URI_LOADBALANCED)=true"
)
}

guard hosts.count <= 1 else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_DIRECTCONNECTION)=true is incompatible with multiple seeds. " +
"got seeds: \(hosts)"
)
}
}

if lb == true {
guard self.replicaSet == nil else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_LOADBALANCED)=true is incompatible with replicaSet option: found option " +
"\(MONGOC_URI_REPLICASET)=\(self.replicaSet ?? "")"
)
}

guard hosts.count <= 1 else {
throw MongoError.InvalidArgumentError(
message: "\(MONGOC_URI_LOADBALANCED)=true is incompatible with multiple seeds. " +
"got seeds: \(hosts)"
)
}
}
}

/// Sets and validates server selection-related on the underlying `mongoc_uri_t`.
Expand Down
5 changes: 5 additions & 0 deletions Sources/MongoSwift/MongoClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public struct MongoClientOptions: CodingStrategyProvider {
/// Defaults to 10 seconds (10,000 ms). Must be at least 500ms.
public var heartbeatFrequencyMS: Int?

/// Indicates whether the driver is connecting to a load balancer.
public var loadBalanced: Bool?

/// The size (in milliseconds) of the permitted latency window beyond the fastest round-trip time amongst all
/// servers. By default, only servers within 15ms of the fastest round-trip time receive queries.
public var localThresholdMS: Int?
Expand Down Expand Up @@ -145,6 +148,7 @@ public struct MongoClientOptions: CodingStrategyProvider {
dateCodingStrategy: DateCodingStrategy? = nil,
directConnection: Bool? = nil,
heartbeatFrequencyMS: Int? = nil,
loadBalanced: Bool? = nil,
localThresholdMS: Int? = nil,
maxPoolSize: Int? = nil,
readConcern: ReadConcern? = nil,
Expand Down Expand Up @@ -173,6 +177,7 @@ public struct MongoClientOptions: CodingStrategyProvider {
self.dateCodingStrategy = dateCodingStrategy
self.directConnection = directConnection
self.heartbeatFrequencyMS = heartbeatFrequencyMS
self.loadBalanced = loadBalanced
self.localThresholdMS = localThresholdMS
self.maxPoolSize = maxPoolSize
self.minHeartbeatFrequencyMS = nil
Expand Down
34 changes: 32 additions & 2 deletions Tests/MongoSwiftTests/ConnectionStringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ let shouldWarnButLibmongocErrors: [String: [String]] = [

// tests we skip because we don't support the specified behavior.
let skipUnsupported: [String: [String]] = [
// we don't support maxIdleTimeMS.
"connection-pool-options.json": [
// we don't support maxIdleTimeMS.
"Too low maxIdleTimeMS causes a warning",
"Non-numeric maxIdleTimeMS causes a warning",
"Valid connection pool options are parsed correctly"
"Valid connection pool options are parsed correctly",
// We don't support minPoolSize.
"minPoolSize=0 does not error"
],
// requires maxIdleTimeMS
"connection-options.json": [
Expand Down Expand Up @@ -581,5 +583,33 @@ final class ConnectionStringTests: MongoSwiftTestCase {
// directConnection=true cannot be used with multiple seeds
expect(try ConnectionString("mongodb://localhost:27017,localhost:27018", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))

// The cases where the conflicting options are all in the connection string are already covered by the URI
// options tests, so here we only check behavior for cases where 1+ option is specified via the options struct.

// loadBalanced=true cannot be used with multiple seeds
opts = MongoClientOptions(loadBalanced: true)
expect(try ConnectionString("mongodb://localhost:27017,localhost:27018", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))

// loadBalanced=true cannot be used with replica set option
expect(try ConnectionString("mongodb://localhost:27017/?replicaSet=xyz", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))
opts.replicaSet = "xyz"
expect(try ConnectionString("mongodb://localhost:27017", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))

// loadBalanced=true cannot be used with directConnection=true
opts = MongoClientOptions(directConnection: true, loadBalanced: true)
expect(try ConnectionString("mongodb://localhost:27017", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))

opts = MongoClientOptions(directConnection: true)
expect(try ConnectionString("mongodb://localhost:27017/?loadBalanced=true", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))

opts = MongoClientOptions(loadBalanced: true)
expect(try ConnectionString("mongodb://localhost:27017/?directConnection=true", options: opts))
.to(throwError(errorType: MongoError.InvalidArgumentError.self))
}
}
70 changes: 70 additions & 0 deletions Tests/Specs/uri-options/tests/connection-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,76 @@
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "loadBalanced=true",
"uri": "mongodb://example.com/?loadBalanced=true",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"loadBalanced": true
}
},
{
"description": "loadBalanced=true with directConnection=false",
"uri": "mongodb://example.com/?loadBalanced=true&directConnection=false",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"loadBalanced": true,
"directConnection": false
}
},
{
"description": "loadBalanced=false",
"uri": "mongodb://example.com/?loadBalanced=false",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"loadBalanced": false
}
},
{
"description": "Invalid loadBalanced value",
"uri": "mongodb://example.com/?loadBalanced=1",
"valid": true,
"warning": true,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "loadBalanced=true with multiple hosts causes an error",
"uri": "mongodb://example1,example2/?loadBalanced=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "loadBalanced=true with directConnection=true causes an error",
"uri": "mongodb://example.com/?loadBalanced=true&directConnection=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "loadBalanced=true with replicaSet causes an error",
"uri": "mongodb://example.com/?loadBalanced=true&replicaSet=replset",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
}
]
}
28 changes: 26 additions & 2 deletions Tests/Specs/uri-options/tests/connection-pool-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
"tests": [
{
"description": "Valid connection pool options are parsed correctly",
"uri": "mongodb://example.com/?maxIdleTimeMS=50000",
"uri": "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"maxIdleTimeMS": 50000
"maxIdleTimeMS": 50000,
"maxPoolSize": 5,
"minPoolSize": 3
}
},
{
Expand All @@ -28,6 +30,28 @@
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "maxPoolSize=0 does not error",
"uri": "mongodb://example.com/?maxPoolSize=0",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"maxPoolSize": 0
}
},
{
"description": "minPoolSize=0 does not error",
"uri": "mongodb://example.com/?minPoolSize=0",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {
"minPoolSize": 0
}
}
]
}