From 8a968b741ad7a249405a68ce0d0d0b86cc5aeb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Wed, 27 May 2026 12:41:20 +0700 Subject: [PATCH 1/2] fix(connections): route open-connection failures through the plugin diagnostic dialog --- .../Infrastructure/WelcomeRouter.swift | 13 ++++--------- TablePro/ViewModels/WelcomeViewModel.swift | 18 ++++++++++++++---- .../Views/Connection/WelcomeWindowView.swift | 5 +++++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/TablePro/Core/Services/Infrastructure/WelcomeRouter.swift b/TablePro/Core/Services/Infrastructure/WelcomeRouter.swift index 221e616ae..7906be595 100644 --- a/TablePro/Core/Services/Infrastructure/WelcomeRouter.swift +++ b/TablePro/Core/Services/Infrastructure/WelcomeRouter.swift @@ -8,10 +8,9 @@ import Combine import Foundation import Observation -internal struct PendingConnectionError: Equatable { - let connectionId: UUID - let connectionName: String - let message: String +internal struct PendingConnectionError { + let connection: DatabaseConnection + let error: Error } @MainActor @@ -52,11 +51,7 @@ internal final class WelcomeRouter { } internal func routeError(_ error: Error, for connection: DatabaseConnection) { - pendingError = PendingConnectionError( - connectionId: connection.id, - connectionName: connection.name, - message: error.localizedDescription - ) + pendingError = PendingConnectionError(connection: connection, error: error) showWelcomeWindow() } diff --git a/TablePro/ViewModels/WelcomeViewModel.swift b/TablePro/ViewModels/WelcomeViewModel.swift index c52584f58..c0c8ea992 100644 --- a/TablePro/ViewModels/WelcomeViewModel.swift +++ b/TablePro/ViewModels/WelcomeViewModel.swift @@ -59,6 +59,7 @@ final class WelcomeViewModel { var connectionError: String? var showConnectionError = false + var pluginDiagnostic: PluginDiagnosticItem? var showImportFilePanel = false var importResultCount: Int? @@ -219,8 +220,7 @@ final class WelcomeViewModel { return } if let pendingError = WelcomeRouter.shared.consumePendingError() { - connectionError = pendingError.message - showConnectionError = true + presentConnectionFailure(pendingError.error, connection: pendingError.connection) } } @@ -609,7 +609,17 @@ final class WelcomeViewModel { Self.logger.error("Failed to connect: \(error.localizedDescription, privacy: .public)") WindowManager.shared.closeWindow(for: connection.id) - connectionError = SSLHandshakeError.formatted(error) - showConnectionError = true + presentConnectionFailure(error, connection: connection) + } + + private func presentConnectionFailure(_ error: Error, connection: DatabaseConnection) { + if let item = PluginDiagnosticItem.classify( + error: error, connection: connection, username: connection.username + ) { + pluginDiagnostic = item + } else { + connectionError = SSLHandshakeError.formatted(error) + showConnectionError = true + } } } diff --git a/TablePro/Views/Connection/WelcomeWindowView.swift b/TablePro/Views/Connection/WelcomeWindowView.swift index b9967ecb0..095bab8da 100644 --- a/TablePro/Views/Connection/WelcomeWindowView.swift +++ b/TablePro/Views/Connection/WelcomeWindowView.swift @@ -143,6 +143,11 @@ struct WelcomeWindowView: View { .pluginInstallPrompt(connection: $vm.pluginInstallConnection) { connection in vm.connectAfterInstall(connection) } + .sheet(item: $vm.pluginDiagnostic) { item in + PluginDiagnosticSheet(item: item) { + vm.pluginDiagnostic = nil + } + } .alert(String(localized: "Rename Group"), isPresented: $vm.showRenameGroupAlert) { TextField(String(localized: "Group name"), text: $vm.renameGroupName) Button(String(localized: "Rename")) { vm.confirmRenameGroup() } From f8927e36235f09f683429797224af18f13ad0d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Wed, 27 May 2026 12:41:21 +0700 Subject: [PATCH 2/2] feat(plugin-oracle): support Oracle Database 11g (11.1 and later) --- CHANGELOG.md | 4 ++++ .../OracleDriverPlugin/OracleConnection.swift | 22 ++++++++++++++++++- Plugins/OracleDriverPlugin/OraclePlugin.swift | 12 +++++----- TablePro.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- docs/databases/oracle.mdx | 10 ++++----- 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c973fcbd4..567ce4b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- BigQuery: the sidebar now shows every dataset as an expandable node, with each dataset's tables loading when you open it, instead of showing one dataset at a time behind a picker. - OpenCode Zen as an AI provider. Add it from the provider list and paste an OpenCode key, or leave the key blank to use the free models; the model list loads automatically, covering the Claude, GPT, Gemini, and open models Zen serves. (#1400) +- Oracle Database 11g (11.1 and 11.2) now connects. Previously only 12c and later worked, so 11g servers failed with a "Server Version Not Supported" error. (#1425) ### Fixed - Custom and OpenAI-compatible AI providers now work when the base URL already ends in `/v1`, instead of building a doubled `/v1/v1/` path that failed. (#1400) - MongoDB: opening a collection no longer crashes when a document contains a NaN or infinite number. (#1418) +- Opening a saved connection that fails now shows the detailed troubleshooting dialog with suggested fixes, the same one Test Connection shows, instead of a generic error alert. (#1425, #483) +- Oracle connection errors no longer surface the driver's raw internal message; failures now explain the cause in plain language. (#483) ## [0.45.0] - 2026-05-26 diff --git a/Plugins/OracleDriverPlugin/OracleConnection.swift b/Plugins/OracleDriverPlugin/OracleConnection.swift index 28e4d7403..4f5d840fe 100644 --- a/Plugins/OracleDriverPlugin/OracleConnection.swift +++ b/Plugins/OracleDriverPlugin/OracleConnection.swift @@ -189,7 +189,11 @@ final class OracleConnectionWrapper: @unchecked Sendable { if let sslError = Self.classifySSLError(detail) { throw sslError } - throw OracleError(message: detail, category: classifyConnectError(sqlError)) + let category = classifyConnectError(sqlError) + throw OracleError( + message: Self.connectErrorMessage(for: category, serverDetail: detail), + category: category + ) } catch let nioSslError as NIOSSLError { let detail = String(describing: nioSslError) osLogger.error("Oracle TLS error: \(detail)") @@ -237,6 +241,22 @@ final class OracleConnectionWrapper: @unchecked Sendable { } } + private static func connectErrorMessage( + for category: OracleError.Category, + serverDetail: String + ) -> String { + switch category { + case .authVersionNotSupported: + return String(localized: "This Oracle server is older than release 11.1, which the database driver does not support.") + case .authConnectionDropped: + return String(localized: "The Oracle server closed the connection during the login handshake.") + case .authVerifierUnsupported: + return String(localized: "This account uses a password verifier the database driver does not support.") + case .generic, .notConnected, .connectionFailed, .queryFailed: + return serverDetail + } + } + func disconnect() { let connection = state.withLock { current -> OracleNIO.OracleConnection? in guard current.isConnected else { return nil } diff --git a/Plugins/OracleDriverPlugin/OraclePlugin.swift b/Plugins/OracleDriverPlugin/OraclePlugin.swift index c18cd2192..fef5f2be3 100644 --- a/Plugins/OracleDriverPlugin/OraclePlugin.swift +++ b/Plugins/OracleDriverPlugin/OraclePlugin.swift @@ -123,9 +123,10 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost title: String(localized: "Connection Dropped During Handshake"), message: oracleError.message, suggestedActions: [ - String(localized: "If the same connection works in DBeaver or sqlplus, this is likely an OOB compatibility issue with cloud-hosted Oracle."), - String(localized: "TablePro 1.2.0 already gates OOB on the server flag, so most cases are resolved. If you still hit this, file an issue."), - String(localized: "Try disabling SSH tunnel or load balancer firewall rules between client and server.") + String(localized: "The server may require Native Network Encryption, which the pure-Swift driver cannot negotiate."), + String(localized: "Configure the listener for TLS, or set SQLNET.ENCRYPTION_SERVER to ACCEPTED instead of REQUIRED."), + String(localized: "If the same connection works in DBeaver or SQL Developer, they use Oracle's OCI client, which supports Native Network Encryption."), + String(localized: "Check for a firewall or load balancer between the client and server that closes connections mid-handshake.") ], supportURL: URL(string: "https://github.com/TableProApp/TablePro/issues/483") ) @@ -134,9 +135,8 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost title: String(localized: "Server Version Not Supported"), message: oracleError.message, suggestedActions: [ - String(localized: "TablePro requires Oracle 12c or later via the OracleNIO Swift driver."), - String(localized: "Check the user account's password_versions; only 10G, 11G, and 12C are supported."), - String(localized: "Rotate the password under modern auth if password_versions contains an unrecognized verifier.") + String(localized: "TablePro supports Oracle Database 11.1 and later. This server reports an older release (10g or earlier)."), + String(localized: "Upgrade the database to 11.2 or later, or connect with a client that bundles Oracle's OCI client such as SQL Developer or DataGrip.") ], supportURL: issuesURL ) diff --git a/TablePro.xcodeproj/project.pbxproj b/TablePro.xcodeproj/project.pbxproj index d2788cc20..3da7054b7 100644 --- a/TablePro.xcodeproj/project.pbxproj +++ b/TablePro.xcodeproj/project.pbxproj @@ -4191,7 +4191,7 @@ repositoryURL = "https://github.com/TableProApp/oracle-nio"; requirement = { kind = revision; - revision = 7c01c8ff2e13794650719ebfa0294aa4281bbdd8; + revision = 254b72adfb6b527ac45895b42a38e60ba6c77a1f; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b48252e9d..4387a72dc 100644 --- a/TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e49cd26950b2b9a687427c0c4359980403a2242976e9d9b5daed883f22e23a11", + "originHash" : "475d5c8ee85aa1d1e11facad23a0ba0c8149e7ea855d30de709ab6e3dd78d3be", "pins" : [ { "identity" : "codeeditsymbols", @@ -15,7 +15,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/TableProApp/oracle-nio", "state" : { - "revision" : "7c01c8ff2e13794650719ebfa0294aa4281bbdd8" + "revision" : "254b72adfb6b527ac45895b42a38e60ba6c77a1f" } }, { diff --git a/docs/databases/oracle.mdx b/docs/databases/oracle.mdx index 9b20235a1..fab4632bd 100644 --- a/docs/databases/oracle.mdx +++ b/docs/databases/oracle.mdx @@ -5,11 +5,11 @@ description: Connect to Oracle Database with TablePro # Oracle Database Connections -TablePro supports Oracle Database 12c and later via Oracle Call Interface (OCI). This covers Oracle Database instances running on-premises, in Docker, or Oracle Cloud. +TablePro supports Oracle Database 11.1 and later. It speaks the Oracle TNS wire protocol directly in Swift, so no Oracle Instant Client or other external library is required. This covers Oracle Database instances running on-premises, in Docker, or Oracle Cloud. - -Oracle Instant Client must be installed before connecting to Oracle Database. Download it from [Oracle's website](https://www.oracle.com/database/technologies/instant-client.html) and ensure the libraries are accessible. - + +Oracle 10g and earlier are not supported: they use the older O3LOGON handshake that the pure-Swift driver does not implement. Servers that require Native Network Encryption also need Oracle's OCI client (use TLS instead). + ## Install Plugin @@ -116,7 +116,7 @@ ALTER USER IDENTIFIED BY ; SELECT username, password_versions FROM dba_users WHERE username = ''; ``` -If the connection fails with the dialog "Server Version Not Supported" or "Unsupported Password Verifier", check `password_versions` first. +If the connection fails with "Unsupported Password Verifier", check `password_versions` first. "Server Version Not Supported" means the database is older than 11.1 (10g or earlier), which the driver cannot connect to. ## Column Type Support