Skip to content

Commit ee613ba

Browse files
authored
feat(plugin-oracle): support Oracle Database 11g (#1429)
* fix(connections): route open-connection failures through the plugin diagnostic dialog * feat(plugin-oracle): support Oracle Database 11g (11.1 and later) --------- Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
1 parent 3ab1297 commit ee613ba

9 files changed

Lines changed: 62 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- 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.
1213
- 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)
14+
- 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)
1315
- Cmd-click a foreign key arrow to open the referenced table in a new tab instead of the current one. The right-click menu has the same Open in New Tab option. (#1421)
1416

1517
### Changed
@@ -21,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2123

2224
- 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)
2325
- MongoDB: opening a collection no longer crashes when a document contains a NaN or infinite number. (#1418)
26+
- 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)
27+
- Oracle connection errors no longer surface the driver's raw internal message; failures now explain the cause in plain language. (#483)
2428

2529
## [0.45.0] - 2026-05-26
2630

Plugins/OracleDriverPlugin/OracleConnection.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,11 @@ final class OracleConnectionWrapper: @unchecked Sendable {
189189
if let sslError = Self.classifySSLError(detail) {
190190
throw sslError
191191
}
192-
throw OracleError(message: detail, category: classifyConnectError(sqlError))
192+
let category = classifyConnectError(sqlError)
193+
throw OracleError(
194+
message: Self.connectErrorMessage(for: category, serverDetail: detail),
195+
category: category
196+
)
193197
} catch let nioSslError as NIOSSLError {
194198
let detail = String(describing: nioSslError)
195199
osLogger.error("Oracle TLS error: \(detail)")
@@ -237,6 +241,22 @@ final class OracleConnectionWrapper: @unchecked Sendable {
237241
}
238242
}
239243

244+
private static func connectErrorMessage(
245+
for category: OracleError.Category,
246+
serverDetail: String
247+
) -> String {
248+
switch category {
249+
case .authVersionNotSupported:
250+
return String(localized: "This Oracle server is older than release 11.1, which the database driver does not support.")
251+
case .authConnectionDropped:
252+
return String(localized: "The Oracle server closed the connection during the login handshake.")
253+
case .authVerifierUnsupported:
254+
return String(localized: "This account uses a password verifier the database driver does not support.")
255+
case .generic, .notConnected, .connectionFailed, .queryFailed:
256+
return serverDetail
257+
}
258+
}
259+
240260
func disconnect() {
241261
let connection = state.withLock { current -> OracleNIO.OracleConnection? in
242262
guard current.isConnected else { return nil }

Plugins/OracleDriverPlugin/OraclePlugin.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,10 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost
123123
title: String(localized: "Connection Dropped During Handshake"),
124124
message: oracleError.message,
125125
suggestedActions: [
126-
String(localized: "If the same connection works in DBeaver or sqlplus, this is likely an OOB compatibility issue with cloud-hosted Oracle."),
127-
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."),
128-
String(localized: "Try disabling SSH tunnel or load balancer firewall rules between client and server.")
126+
String(localized: "The server may require Native Network Encryption, which the pure-Swift driver cannot negotiate."),
127+
String(localized: "Configure the listener for TLS, or set SQLNET.ENCRYPTION_SERVER to ACCEPTED instead of REQUIRED."),
128+
String(localized: "If the same connection works in DBeaver or SQL Developer, they use Oracle's OCI client, which supports Native Network Encryption."),
129+
String(localized: "Check for a firewall or load balancer between the client and server that closes connections mid-handshake.")
129130
],
130131
supportURL: URL(string: "https://github.com/TableProApp/TablePro/issues/483")
131132
)
@@ -134,9 +135,8 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost
134135
title: String(localized: "Server Version Not Supported"),
135136
message: oracleError.message,
136137
suggestedActions: [
137-
String(localized: "TablePro requires Oracle 12c or later via the OracleNIO Swift driver."),
138-
String(localized: "Check the user account's password_versions; only 10G, 11G, and 12C are supported."),
139-
String(localized: "Rotate the password under modern auth if password_versions contains an unrecognized verifier.")
138+
String(localized: "TablePro supports Oracle Database 11.1 and later. This server reports an older release (10g or earlier)."),
139+
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.")
140140
],
141141
supportURL: issuesURL
142142
)

TablePro.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4191,7 +4191,7 @@
41914191
repositoryURL = "https://github.com/TableProApp/oracle-nio";
41924192
requirement = {
41934193
kind = revision;
4194-
revision = 7c01c8ff2e13794650719ebfa0294aa4281bbdd8;
4194+
revision = 254b72adfb6b527ac45895b42a38e60ba6c77a1f;
41954195
};
41964196
};
41974197
/* End XCRemoteSwiftPackageReference section */

TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TablePro/Core/Services/Infrastructure/WelcomeRouter.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import Combine
88
import Foundation
99
import Observation
1010

11-
internal struct PendingConnectionError: Equatable {
12-
let connectionId: UUID
13-
let connectionName: String
14-
let message: String
11+
internal struct PendingConnectionError {
12+
let connection: DatabaseConnection
13+
let error: Error
1514
}
1615

1716
@MainActor
@@ -52,11 +51,7 @@ internal final class WelcomeRouter {
5251
}
5352

5453
internal func routeError(_ error: Error, for connection: DatabaseConnection) {
55-
pendingError = PendingConnectionError(
56-
connectionId: connection.id,
57-
connectionName: connection.name,
58-
message: error.localizedDescription
59-
)
54+
pendingError = PendingConnectionError(connection: connection, error: error)
6055
showWelcomeWindow()
6156
}
6257

TablePro/ViewModels/WelcomeViewModel.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ final class WelcomeViewModel {
5959

6060
var connectionError: String?
6161
var showConnectionError = false
62+
var pluginDiagnostic: PluginDiagnosticItem?
6263

6364
var showImportFilePanel = false
6465
var importResultCount: Int?
@@ -219,8 +220,7 @@ final class WelcomeViewModel {
219220
return
220221
}
221222
if let pendingError = WelcomeRouter.shared.consumePendingError() {
222-
connectionError = pendingError.message
223-
showConnectionError = true
223+
presentConnectionFailure(pendingError.error, connection: pendingError.connection)
224224
}
225225
}
226226

@@ -609,7 +609,17 @@ final class WelcomeViewModel {
609609

610610
Self.logger.error("Failed to connect: \(error.localizedDescription, privacy: .public)")
611611
WindowManager.shared.closeWindow(for: connection.id)
612-
connectionError = SSLHandshakeError.formatted(error)
613-
showConnectionError = true
612+
presentConnectionFailure(error, connection: connection)
613+
}
614+
615+
private func presentConnectionFailure(_ error: Error, connection: DatabaseConnection) {
616+
if let item = PluginDiagnosticItem.classify(
617+
error: error, connection: connection, username: connection.username
618+
) {
619+
pluginDiagnostic = item
620+
} else {
621+
connectionError = SSLHandshakeError.formatted(error)
622+
showConnectionError = true
623+
}
614624
}
615625
}

TablePro/Views/Connection/WelcomeWindowView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ struct WelcomeWindowView: View {
143143
.pluginInstallPrompt(connection: $vm.pluginInstallConnection) { connection in
144144
vm.connectAfterInstall(connection)
145145
}
146+
.sheet(item: $vm.pluginDiagnostic) { item in
147+
PluginDiagnosticSheet(item: item) {
148+
vm.pluginDiagnostic = nil
149+
}
150+
}
146151
.alert(String(localized: "Rename Group"), isPresented: $vm.showRenameGroupAlert) {
147152
TextField(String(localized: "Group name"), text: $vm.renameGroupName)
148153
Button(String(localized: "Rename")) { vm.confirmRenameGroup() }

docs/databases/oracle.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ description: Connect to Oracle Database with TablePro
55

66
# Oracle Database Connections
77

8-
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.
8+
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.
99

10-
<Warning>
11-
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.
12-
</Warning>
10+
<Note>
11+
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).
12+
</Note>
1313

1414
## Install Plugin
1515

@@ -116,7 +116,7 @@ ALTER USER <USER> IDENTIFIED BY <new-secure-password>;
116116
SELECT username, password_versions FROM dba_users WHERE username = '<USER>';
117117
```
118118

119-
If the connection fails with the dialog "Server Version Not Supported" or "Unsupported Password Verifier", check `password_versions` first.
119+
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.
120120

121121
## Column Type Support
122122

0 commit comments

Comments
 (0)