Skip to content

Commit 09755c8

Browse files
committed
feat: add MongoDB configurable authSource and fix connection field persistence
1 parent d8c2aa3 commit 09755c8

File tree

9 files changed

+59
-2
lines changed

9 files changed

+59
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- DuckDB database support — connect to `.duckdb` files, query CSV/Parquet/JSON files via SQL, schema navigation, and DuckDB extension management
13+
- MongoDB configurable auth database (`authSource`) — authenticate against any database instead of hardcoded `admin`
1314

1415
### Fixed
1516

17+
- MongoDB Read Preference, Write Concern, and Redis Database not persisted across app restarts
18+
1619
- Result truncation at 100K rows now reported to UI via `PluginQueryResult.isTruncated` instead of being silently discarded
1720
- DELETE and UPDATE queries using all columns in WHERE clause instead of just the primary key for PostgreSQL, Redshift, MSSQL, and ClickHouse
1821
- SSL/TLS always being enabled for MongoDB, Redis, and ClickHouse connections due to case mismatch in SSL mode string comparison (#249)

Plugins/MongoDBDriverPlugin/MongoDBConnection.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ final class MongoDBConnection: @unchecked Sendable {
5959
private let sslMode: String
6060
private let sslCACertPath: String
6161
private let sslClientCertPath: String
62+
private let authSource: String?
6263
private let readPreference: String?
6364
private let writeConcern: String?
6465

@@ -111,6 +112,7 @@ final class MongoDBConnection: @unchecked Sendable {
111112
sslMode: String = "Disabled",
112113
sslCACertPath: String = "",
113114
sslClientCertPath: String = "",
115+
authSource: String? = nil,
114116
readPreference: String? = nil,
115117
writeConcern: String? = nil
116118
) {
@@ -122,6 +124,7 @@ final class MongoDBConnection: @unchecked Sendable {
122124
self.sslMode = sslMode
123125
self.sslCACertPath = sslCACertPath
124126
self.sslClientCertPath = sslClientCertPath
127+
self.authSource = authSource
125128
self.readPreference = readPreference
126129
self.writeConcern = writeConcern
127130
}
@@ -167,10 +170,11 @@ final class MongoDBConnection: @unchecked Sendable {
167170
uri += "\(encodedHost):\(port)"
168171
uri += database.isEmpty ? "/" : "/\(encodedDb)"
169172

173+
let effectiveAuthSource = authSource.flatMap { $0.isEmpty ? nil : $0 } ?? "admin"
170174
var params: [String] = [
171175
"connectTimeoutMS=10000",
172176
"serverSelectionTimeoutMS=10000",
173-
"authSource=admin"
177+
"authSource=\(effectiveAuthSource)"
174178
]
175179

176180
let sslEnabled = ["Preferred", "Required", "Verify CA", "Verify Identity"].contains(sslMode)

Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class MongoDBPlugin: NSObject, TableProPlugin, DriverPlugin {
1717
static let iconName = "leaf.fill"
1818
static let defaultPort = 27017
1919
static let additionalConnectionFields: [ConnectionField] = [
20+
ConnectionField(id: "mongoAuthSource", label: "Auth Database", placeholder: "admin"),
2021
ConnectionField(id: "mongoReadPreference", label: "Read Preference", placeholder: "primary"),
2122
ConnectionField(id: "mongoWriteConcern", label: "Write Concern", placeholder: "majority")
2223
]

Plugins/MongoDBDriverPlugin/MongoDBPluginDriver.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ final class MongoDBPluginDriver: PluginDatabaseDriver {
4040
sslMode: config.additionalFields["sslMode"] ?? "Disabled",
4141
sslCACertPath: config.additionalFields["sslCACertPath"] ?? "",
4242
sslClientCertPath: config.additionalFields["sslClientCertPath"] ?? "",
43+
authSource: config.additionalFields["mongoAuthSource"],
4344
readPreference: config.additionalFields["mongoReadPreference"],
4445
writeConcern: config.additionalFields["mongoWriteConcern"]
4546
)

TablePro/AppDelegate+ConnectionHandler.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ extension AppDelegate {
404404
sslConfig: sslConfig,
405405
color: color,
406406
tagId: tagId,
407+
mongoAuthSource: parsed.authSource,
407408
redisDatabase: parsed.redisDatabase,
408409
oracleServiceName: parsed.oracleServiceName
409410
)

TablePro/Core/Database/DatabaseDriver.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ enum DatabaseDriverFactory {
346346
switch connection.type {
347347
case .mongodb:
348348
fields["sslCACertPath"] = ssl.caCertificatePath
349+
fields["mongoAuthSource"] = connection.mongoAuthSource ?? ""
349350
fields["mongoReadPreference"] = connection.mongoReadPreference ?? ""
350351
fields["mongoWriteConcern"] = connection.mongoWriteConcern ?? ""
351352
case .redis:

TablePro/Core/Storage/ConnectionStorage.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ final class ConnectionStorage {
121121
groupId: connection.groupId,
122122
safeModeLevel: connection.safeModeLevel,
123123
aiPolicy: connection.aiPolicy,
124+
mongoAuthSource: connection.mongoAuthSource,
124125
mongoReadPreference: connection.mongoReadPreference,
125126
mongoWriteConcern: connection.mongoWriteConcern,
126127
redisDatabase: connection.redisDatabase,
@@ -368,6 +369,14 @@ private struct StoredConnection: Codable {
368369
// AI policy
369370
let aiPolicy: String?
370371

372+
// MongoDB-specific
373+
let mongoAuthSource: String?
374+
let mongoReadPreference: String?
375+
let mongoWriteConcern: String?
376+
377+
// Redis-specific
378+
let redisDatabase: Int?
379+
371380
// MSSQL schema
372381
let mssqlSchema: String?
373382

@@ -413,6 +422,14 @@ private struct StoredConnection: Codable {
413422
// AI policy
414423
self.aiPolicy = connection.aiPolicy?.rawValue
415424

425+
// MongoDB-specific
426+
self.mongoAuthSource = connection.mongoAuthSource
427+
self.mongoReadPreference = connection.mongoReadPreference
428+
self.mongoWriteConcern = connection.mongoWriteConcern
429+
430+
// Redis-specific
431+
self.redisDatabase = connection.redisDatabase
432+
416433
// MSSQL schema
417434
self.mssqlSchema = connection.mssqlSchema
418435

@@ -431,7 +448,9 @@ private struct StoredConnection: Codable {
431448
case color, tagId, groupId
432449
case safeModeLevel
433450
case isReadOnly // Legacy key for migration reading only
434-
case aiPolicy, mssqlSchema, oracleServiceName, startupCommands
451+
case aiPolicy
452+
case mongoAuthSource, mongoReadPreference, mongoWriteConcern, redisDatabase
453+
case mssqlSchema, oracleServiceName, startupCommands
435454
}
436455

437456
func encode(to encoder: Encoder) throws {
@@ -460,6 +479,10 @@ private struct StoredConnection: Codable {
460479
try container.encodeIfPresent(groupId, forKey: .groupId)
461480
try container.encode(safeModeLevel, forKey: .safeModeLevel)
462481
try container.encodeIfPresent(aiPolicy, forKey: .aiPolicy)
482+
try container.encodeIfPresent(mongoAuthSource, forKey: .mongoAuthSource)
483+
try container.encodeIfPresent(mongoReadPreference, forKey: .mongoReadPreference)
484+
try container.encodeIfPresent(mongoWriteConcern, forKey: .mongoWriteConcern)
485+
try container.encodeIfPresent(redisDatabase, forKey: .redisDatabase)
463486
try container.encodeIfPresent(mssqlSchema, forKey: .mssqlSchema)
464487
try container.encodeIfPresent(oracleServiceName, forKey: .oracleServiceName)
465488
try container.encodeIfPresent(startupCommands, forKey: .startupCommands)
@@ -506,6 +529,10 @@ private struct StoredConnection: Codable {
506529
safeModeLevel = wasReadOnly ? SafeModeLevel.readOnly.rawValue : SafeModeLevel.silent.rawValue
507530
}
508531
aiPolicy = try container.decodeIfPresent(String.self, forKey: .aiPolicy)
532+
mongoAuthSource = try container.decodeIfPresent(String.self, forKey: .mongoAuthSource)
533+
mongoReadPreference = try container.decodeIfPresent(String.self, forKey: .mongoReadPreference)
534+
mongoWriteConcern = try container.decodeIfPresent(String.self, forKey: .mongoWriteConcern)
535+
redisDatabase = try container.decodeIfPresent(Int.self, forKey: .redisDatabase)
509536
mssqlSchema = try container.decodeIfPresent(String.self, forKey: .mssqlSchema)
510537
oracleServiceName = try container.decodeIfPresent(String.self, forKey: .oracleServiceName)
511538
startupCommands = try container.decodeIfPresent(String.self, forKey: .startupCommands)
@@ -550,6 +577,10 @@ private struct StoredConnection: Codable {
550577
groupId: parsedGroupId,
551578
safeModeLevel: SafeModeLevel(rawValue: safeModeLevel) ?? .silent,
552579
aiPolicy: parsedAIPolicy,
580+
mongoAuthSource: mongoAuthSource,
581+
mongoReadPreference: mongoReadPreference,
582+
mongoWriteConcern: mongoWriteConcern,
583+
redisDatabase: redisDatabase,
553584
mssqlSchema: mssqlSchema,
554585
oracleServiceName: oracleServiceName,
555586
startupCommands: startupCommands

TablePro/Models/Connection/DatabaseConnection.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ struct DatabaseConnection: Identifiable, Hashable {
405405
var groupId: UUID?
406406
var safeModeLevel: SafeModeLevel
407407
var aiPolicy: AIConnectionPolicy?
408+
var mongoAuthSource: String?
408409
var mongoReadPreference: String?
409410
var mongoWriteConcern: String?
410411
var redisDatabase: Int?
@@ -427,6 +428,7 @@ struct DatabaseConnection: Identifiable, Hashable {
427428
groupId: UUID? = nil,
428429
safeModeLevel: SafeModeLevel = .silent,
429430
aiPolicy: AIConnectionPolicy? = nil,
431+
mongoAuthSource: String? = nil,
430432
mongoReadPreference: String? = nil,
431433
mongoWriteConcern: String? = nil,
432434
redisDatabase: Int? = nil,
@@ -448,6 +450,7 @@ struct DatabaseConnection: Identifiable, Hashable {
448450
self.groupId = groupId
449451
self.safeModeLevel = safeModeLevel
450452
self.aiPolicy = aiPolicy
453+
self.mongoAuthSource = mongoAuthSource
451454
self.mongoReadPreference = mongoReadPreference
452455
self.mongoWriteConcern = mongoWriteConcern
453456
self.redisDatabase = redisDatabase

TablePro/Views/Connection/ConnectionFormView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ struct ConnectionFormView: View {
7272
@State private var aiPolicy: AIConnectionPolicy?
7373

7474
// MongoDB-specific settings
75+
@State private var mongoAuthSource: String = ""
7576
@State private var mongoReadPreference: String = ""
7677
@State private var mongoWriteConcern: String = ""
7778

@@ -572,6 +573,11 @@ struct ConnectionFormView: View {
572573
Form {
573574
if type == .mongodb {
574575
Section("MongoDB") {
576+
TextField(
577+
String(localized: "Auth Database"),
578+
text: $mongoAuthSource,
579+
prompt: Text("admin")
580+
)
575581
Picker(String(localized: "Read Preference"), selection: $mongoReadPreference) {
576582
Text(String(localized: "Default")).tag("")
577583
Text("Primary").tag("primary")
@@ -813,6 +819,7 @@ struct ConnectionFormView: View {
813819
aiPolicy = existing.aiPolicy
814820

815821
// Load MongoDB settings
822+
mongoAuthSource = existing.mongoAuthSource ?? ""
816823
mongoReadPreference = existing.mongoReadPreference ?? ""
817824
mongoWriteConcern = existing.mongoWriteConcern ?? ""
818825

@@ -884,6 +891,7 @@ struct ConnectionFormView: View {
884891
groupId: selectedGroupId,
885892
safeModeLevel: safeModeLevel,
886893
aiPolicy: aiPolicy,
894+
mongoAuthSource: mongoAuthSource.isEmpty ? nil : mongoAuthSource,
887895
mongoReadPreference: mongoReadPreference.isEmpty ? nil : mongoReadPreference,
888896
mongoWriteConcern: mongoWriteConcern.isEmpty ? nil : mongoWriteConcern,
889897
mssqlSchema: mssqlSchema.isEmpty ? nil : mssqlSchema,
@@ -1015,6 +1023,7 @@ struct ConnectionFormView: View {
10151023
color: connectionColor,
10161024
tagId: selectedTagId,
10171025
groupId: selectedGroupId,
1026+
mongoAuthSource: mongoAuthSource.isEmpty ? nil : mongoAuthSource,
10181027
mongoReadPreference: mongoReadPreference.isEmpty ? nil : mongoReadPreference,
10191028
mongoWriteConcern: mongoWriteConcern.isEmpty ? nil : mongoWriteConcern,
10201029
mssqlSchema: mssqlSchema.isEmpty ? nil : mssqlSchema,
@@ -1154,6 +1163,9 @@ struct ConnectionFormView: View {
11541163
applySSHAgentSocketPath(parsed.agentSocket ?? "")
11551164
}
11561165
}
1166+
if let authSourceValue = parsed.authSource, !authSourceValue.isEmpty {
1167+
mongoAuthSource = authSourceValue
1168+
}
11571169
if let connectionName = parsed.connectionName, !connectionName.isEmpty {
11581170
name = connectionName
11591171
} else if name.isEmpty {

0 commit comments

Comments
 (0)