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
59 changes: 30 additions & 29 deletions Plugins/OracleDriverPlugin/OracleConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,16 @@ final class OracleConnectionWrapper: @unchecked Sendable {
private let database: String
private let serviceName: String

private let lock = NSLock()
private var _isConnected = false
private var nioConnection: OracleNIO.OracleConnection?
private struct LockedState: Sendable {
var isConnected = false
var nioConnection: OracleNIO.OracleConnection?
}

private let state = OSAllocatedUnfairLock(initialState: LockedState())
private let nioLogger = Logging.Logger(label: "com.TablePro.oracle-nio")

var isConnected: Bool {
lock.lock()
defer { lock.unlock() }
return _isConnected
state.withLock { $0.isConnected }
}

// MARK: - Initialization
Expand Down Expand Up @@ -163,10 +164,10 @@ final class OracleConnectionWrapper: @unchecked Sendable {
logger: nioLogger
)

lock.lock()
nioConnection = connection
_isConnected = true
lock.unlock()
state.withLock { current in
current.nioConnection = connection
current.isConnected = true
}

osLogger.debug("Connected to Oracle \(self.host):\(self.port)/\(service)")
} catch let sqlError as OracleSQLError {
Expand Down Expand Up @@ -196,31 +197,31 @@ final class OracleConnectionWrapper: @unchecked Sendable {
}

func disconnect() {
lock.lock()
guard _isConnected else {
lock.unlock()
return
let connection = state.withLock { current -> OracleNIO.OracleConnection? in
guard current.isConnected else { return nil }
current.isConnected = false
let conn = current.nioConnection
current.nioConnection = nil
return conn
}
_isConnected = false
let connection = nioConnection
nioConnection = nil
lock.unlock()

guard let connection else { return }

Task {
try? await connection?.close()
try? await connection.close()
osLogger.debug("Disconnected from Oracle \(self.host):\(self.port)")
}
}

// MARK: - Query Execution

func executeQuery(_ query: String) async throws -> OracleQueryResult {
lock.lock()
guard let connection = nioConnection, _isConnected else {
lock.unlock()
throw OracleError.notConnected
let connection = try state.withLock { current -> OracleNIO.OracleConnection in
guard let conn = current.nioConnection, current.isConnected else {
throw OracleError.notConnected
}
return conn
}
lock.unlock()

// OracleNIO does not support concurrent queries on a single connection.
// Serialize all queries to prevent state-machine corruption.
Expand Down Expand Up @@ -296,12 +297,12 @@ final class OracleConnectionWrapper: @unchecked Sendable {
_ query: String,
continuation: AsyncThrowingStream<PluginStreamElement, Error>.Continuation
) async throws {
lock.lock()
guard let connection = nioConnection, _isConnected else {
lock.unlock()
throw OracleError.notConnected
let connection = try state.withLock { current -> OracleNIO.OracleConnection in
guard let conn = current.nioConnection, current.isConnected else {
throw OracleError.notConnected
}
return conn
}
lock.unlock()

await queryGate.acquire()

Expand Down
90 changes: 45 additions & 45 deletions Plugins/OracleDriverPlugin/OraclePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,51 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost
func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver {
OraclePluginDriver(config: config)
}

func diagnose(error: Error) -> PluginDiagnostic? {
guard let oracleError = error as? OracleError else { return nil }
let issuesURL = URL(string: "https://github.com/TableProApp/TablePro/issues")
switch oracleError.category {
case .authVerifierUnsupported(let flag):
return PluginDiagnostic(
title: String(localized: "Unsupported Password Verifier"),
message: oracleError.message,
suggestedActions: [
String(localized: "Verify the user account exists and the password is correct."),
String(localized: "Ask your DBA to confirm the user has an 11G or 12C password verifier (SELECT password_versions FROM dba_users WHERE username = '<USER>')."),
String(localized: "If the verifier is brand-new (e.g. 23ai), file an issue with the verifier flag below.")
],
diagnosticInfo: [
DiagnosticEntry(label: "Verifier flag", value: flag)
],
supportURL: issuesURL
)
case .authConnectionDropped:
return PluginDiagnostic(
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.")
],
supportURL: URL(string: "https://github.com/TableProApp/TablePro/issues/483")
)
case .authVersionNotSupported:
return PluginDiagnostic(
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.")
],
supportURL: issuesURL
)
case .generic, .notConnected, .connectionFailed, .queryFailed:
return nil
}
}
}

final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
Expand Down Expand Up @@ -1104,51 +1149,6 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
return raw.replacingOccurrences(of: "'", with: "''")
}

func diagnose(error: Error) -> PluginDiagnostic? {
guard let oracleError = error as? OracleError else { return nil }
let issuesURL = URL(string: "https://github.com/TableProApp/TablePro/issues")
switch oracleError.category {
case .authVerifierUnsupported(let flag):
return PluginDiagnostic(
title: String(localized: "Unsupported Password Verifier"),
message: oracleError.message,
suggestedActions: [
String(localized: "Verify the user account exists and the password is correct."),
String(localized: "Ask your DBA to confirm the user has an 11G or 12C password verifier (SELECT password_versions FROM dba_users WHERE username = '<USER>')."),
String(localized: "If the verifier is brand-new (e.g. 23ai), file an issue with the verifier flag below.")
],
diagnosticInfo: [
DiagnosticEntry(label: "Verifier flag", value: flag)
],
supportURL: issuesURL
)
case .authConnectionDropped:
return PluginDiagnostic(
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.")
],
supportURL: URL(string: "https://github.com/TableProApp/TablePro/issues/483")
)
case .authVersionNotSupported:
return PluginDiagnostic(
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.")
],
supportURL: issuesURL
)
case .generic, .notConnected, .connectionFailed, .queryFailed:
return nil
}
}

private static let fromTableRegex = try? NSRegularExpression(
pattern: #"FROM\s+(?:"([^"]+)"|(\w+))"#,
options: .caseInsensitive
Expand Down
Loading