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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Importing connections from DBeaver now brings over the username (#1355)

## [0.43.1] - 2026-05-20

### Added
Expand Down
21 changes: 12 additions & 9 deletions TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,20 @@ struct DBeaverImporter: ForeignAppImporter {

let foldersDict = json["folders"] as? [String: [String: Any]] ?? [:]

var credentialsMap: [String: [String: Any]] = [:]
if includePasswords {
let credentialsURL = dataSourcesURL.deletingLastPathComponent()
.appendingPathComponent("credentials-config.json")
credentialsMap = loadCredentials(from: credentialsURL)
}
let credentialsURL = dataSourcesURL.deletingLastPathComponent()
.appendingPathComponent("credentials-config.json")
let credentialsMap = loadCredentials(from: credentialsURL)

var exportableConnections: [ExportableConnection] = []
var groupNames: Set<String> = []
var credentials: [String: ExportableCredentials] = [:]

for (connId, connDict) in connectionsDict {
do {
let conn = try parseConnection(connId, dict: connDict, folders: foldersDict)
let credentialUsername = (credentialsMap[connId]?["#connection"] as? [String: Any])?["user"] as? String
let conn = try parseConnection(
connId, dict: connDict, folders: foldersDict, credentialUsername: credentialUsername
)
let index = exportableConnections.count
exportableConnections.append(conn)

Expand Down Expand Up @@ -159,7 +159,8 @@ struct DBeaverImporter: ForeignAppImporter {
private func parseConnection(
_ connId: String,
dict: [String: Any],
folders: [String: [String: Any]]
folders: [String: [String: Any]],
credentialUsername: String?
) throws -> ExportableConnection {
let name = dict["name"] as? String ?? connId
let provider = dict["provider"] as? String ?? ""
Expand All @@ -176,7 +177,9 @@ struct DBeaverImporter: ForeignAppImporter {
port = defaultPort(for: dbType)
}
let database = config["database"] as? String ?? config["url"] as? String ?? ""
let username = config["user"] as? String ?? ""
let username = [credentialUsername, config["user"] as? String]
.compactMap { $0 }
.first { !$0.isEmpty } ?? ""

let folderPath = dict["folder"] as? String
let groupName: String?
Expand Down
69 changes: 66 additions & 3 deletions TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ struct DBeaverImporterTests {
provider: String = "postgresql",
host: String = "db.example.com",
port: Any? = 5432,
user: String = "admin",
user: String? = "admin",
database: String = "mydb",
folder: String? = nil,
sshEnabled: Bool = false,
Expand All @@ -95,9 +95,11 @@ struct DBeaverImporterTests {
) -> [String: Any] {
var config: [String: Any] = [
"host": host,
"user": user,
"database": database
]
if let user = user {
config["user"] = user
}
if let port = port {
config["port"] = port
}
Expand Down Expand Up @@ -442,13 +444,74 @@ struct DBeaverImporterTests {
"pg-1": makeConnection(name: "PG")
]
try writeDataSources(makeDataSourcesJSON(connections: connections))
// Even if credentials file exists, it should not be read
try writeCredentials(["pg-1": ["#connection": ["password": "secret"]]])

let result = try importer.importConnections(includePasswords: false)
#expect(result.envelope.credentials == nil)
}

// MARK: - Username (credentials-config.json)

@Test("Username imports from credentials-config.json")
func testImportConnections_usernameFromCredentials() throws {
let connections: [String: [String: Any]] = [
"pg-1": makeConnection(name: "PG", user: nil)
]
try writeDataSources(makeDataSourcesJSON(connections: connections))
try writeCredentials(["pg-1": ["#connection": ["user": "sameer", "password": "p"]]])

let result = try importer.importConnections(includePasswords: true)
#expect(result.envelope.connections[0].username == "sameer")
}

@Test("Username imports even when passwords are excluded")
func testImportConnections_usernameImportsWithoutPasswords() throws {
let connections: [String: [String: Any]] = [
"pg-1": makeConnection(name: "PG", user: nil)
]
try writeDataSources(makeDataSourcesJSON(connections: connections))
try writeCredentials(["pg-1": ["#connection": ["user": "sameer", "password": "p"]]])

let result = try importer.importConnections(includePasswords: false)
#expect(result.envelope.connections[0].username == "sameer")
#expect(result.envelope.credentials == nil)
}

@Test("Username falls back to data-sources configuration.user")
func testImportConnections_usernameFallsBackToConfig() throws {
let connections: [String: [String: Any]] = [
"pg-1": makeConnection(name: "PG", user: "configuser")
]
try writeDataSources(makeDataSourcesJSON(connections: connections))

let result = try importer.importConnections(includePasswords: true)
#expect(result.envelope.connections[0].username == "configuser")
}

@Test("Credentials username takes precedence over configuration.user")
func testImportConnections_credentialsUsernameWins() throws {
let connections: [String: [String: Any]] = [
"pg-1": makeConnection(name: "PG", user: "configuser")
]
try writeDataSources(makeDataSourcesJSON(connections: connections))
try writeCredentials(["pg-1": ["#connection": ["user": "creduser"]]])

let result = try importer.importConnections(includePasswords: true)
#expect(result.envelope.connections[0].username == "creduser")
}

@Test("Empty credentials username falls back to configuration.user")
func testImportConnections_emptyCredentialsUsernameFallsBack() throws {
let connections: [String: [String: Any]] = [
"pg-1": makeConnection(name: "PG", user: "configuser")
]
try writeDataSources(makeDataSourcesJSON(connections: connections))
try writeCredentials(["pg-1": ["#connection": ["user": ""]]])

let result = try importer.importConnections(includePasswords: true)
#expect(result.envelope.connections[0].username == "configuser")
}

@Test("importConnections invalid JSON throws parse error")
func testImportConnections_invalidJSON_throwsParseError() throws {
// Write invalid data to data-sources.json
Expand Down
Loading