-
-
Notifications
You must be signed in to change notification settings - Fork 297
feat(structure): triggers for more engines, metadata columns, filter/sort, persisted layout #1703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
35f3417
7c06e51
b105478
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost | |
| // MARK: - UI/Capability Metadata | ||
|
|
||
| static let isDownloadable = true | ||
| static let supportsTriggers = true | ||
| static let pathFieldRole: PathFieldRole = .serviceName | ||
| static let supportsForeignKeyDisable = false | ||
| static let supportsSchemaSwitching = true | ||
|
|
@@ -467,6 +468,50 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable { | |
| } | ||
| } | ||
|
|
||
| func fetchTriggers(table: String, schema: String?) async throws -> [PluginTriggerInfo] { | ||
| let escapedTable = table.replacingOccurrences(of: "'", with: "''") | ||
| let escaped = effectiveSchemaEscaped(schema) | ||
| let sql = """ | ||
| SELECT TRIGGER_NAME, TRIGGER_TYPE, TRIGGERING_EVENT, STATUS, WHEN_CLAUSE | ||
| FROM ALL_TRIGGERS | ||
| WHERE TABLE_OWNER = '\(escaped)' | ||
| AND TABLE_NAME = '\(escapedTable)' | ||
| ORDER BY TRIGGER_NAME | ||
| """ | ||
| let result = try await execute(query: sql) | ||
| return result.rows.compactMap { row -> PluginTriggerInfo? in | ||
| guard let name = row[safe: 0]?.asText else { return nil } | ||
| let triggerType = (row[safe: 1]?.asText ?? "").uppercased() | ||
| let event = row[safe: 2]?.asText ?? "" | ||
| let timing: String | ||
| if triggerType.contains("INSTEAD OF") { | ||
| timing = "INSTEAD OF" | ||
| } else if triggerType.hasPrefix("BEFORE") { | ||
| timing = "BEFORE" | ||
| } else { | ||
| timing = "AFTER" | ||
| } | ||
| let isRowLevel = triggerType.contains("EACH ROW") | ||
| let enabled = (row[safe: 3]?.asText ?? "").uppercased() == "ENABLED" | ||
| let whenClause = row[safe: 4]?.asText | ||
| let quotedName = "\"\(name.replacingOccurrences(of: "\"", with: "\"\""))\"" | ||
| let quotedTable = "\"\(table.replacingOccurrences(of: "\"", with: "\"\""))\"" | ||
| let forEach = isRowLevel ? " FOR EACH ROW" : "" | ||
| let whenLine = (whenClause?.isEmpty == false) ? "\n WHEN (\(whenClause ?? ""))" : "" | ||
| let statement = """ | ||
| CREATE OR REPLACE TRIGGER \(quotedName) | ||
| \(timing) \(event) ON \(quotedTable)\(forEach)\(whenLine) | ||
| """ | ||
|
Comment on lines
+501
to
+504
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Oracle connections with any table trigger, Useful? React with 👍 / 👎. |
||
| return PluginTriggerInfo( | ||
| name: name, | ||
| timing: timing, | ||
| event: event, | ||
| statement: statement, | ||
| enabled: enabled | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| func fetchAllColumns(schema: String?) async throws -> [String: [PluginColumnInfo]] { | ||
| let escaped = effectiveSchemaEscaped(schema) | ||
| let sql = """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,16 +12,19 @@ public struct PluginTriggerInfo: Codable, Sendable { | |
| public let timing: String | ||
| public let event: String | ||
| public let statement: String | ||
| public let enabled: Bool? | ||
|
|
||
| public init( | ||
| name: String, | ||
| timing: String, | ||
| event: String, | ||
| statement: String | ||
| statement: String, | ||
| enabled: Bool? = nil | ||
|
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Adding Useful? React with 👍 / 👎. |
||
| ) { | ||
| self.name = name | ||
| self.timing = timing | ||
| self.event = event | ||
| self.statement = statement | ||
| self.enabled = enabled | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // | ||
| // AutosavingVSplitView.swift | ||
| // TablePro | ||
| // | ||
| // A vertically stacked split view whose divider position persists via NSSplitView.autosaveName. | ||
| // | ||
|
|
||
| import AppKit | ||
| import SwiftUI | ||
|
|
||
| struct AutosavingVSplitView<Top: View, Bottom: View>: NSViewControllerRepresentable { | ||
| let autosaveName: String | ||
| let topMinimumHeight: CGFloat | ||
| let bottomMinimumHeight: CGFloat | ||
| @ViewBuilder let top: () -> Top | ||
| @ViewBuilder let bottom: () -> Bottom | ||
|
|
||
| func makeCoordinator() -> Coordinator { Coordinator() } | ||
|
|
||
| func makeNSViewController(context: Context) -> NSSplitViewController { | ||
| let controller = NSSplitViewController() | ||
| controller.splitView.isVertical = false | ||
| controller.splitView.dividerStyle = .thin | ||
|
|
||
| let topController = NSHostingController(rootView: top()) | ||
| let bottomController = NSHostingController(rootView: bottom()) | ||
| context.coordinator.topController = topController | ||
| context.coordinator.bottomController = bottomController | ||
|
|
||
| let topItem = NSSplitViewItem(viewController: topController) | ||
| topItem.minimumThickness = topMinimumHeight | ||
| topItem.canCollapse = false | ||
| let bottomItem = NSSplitViewItem(viewController: bottomController) | ||
| bottomItem.minimumThickness = bottomMinimumHeight | ||
| bottomItem.canCollapse = false | ||
|
|
||
| controller.addSplitViewItem(topItem) | ||
| controller.addSplitViewItem(bottomItem) | ||
| controller.splitView.autosaveName = NSSplitView.AutosaveName(autosaveName) | ||
| return controller | ||
| } | ||
|
|
||
| func updateNSViewController(_ controller: NSSplitViewController, context: Context) { | ||
| context.coordinator.topController?.rootView = top() | ||
| context.coordinator.bottomController?.rootView = bottom() | ||
| } | ||
|
|
||
| final class Coordinator { | ||
| var topController: NSHostingController<Top>? | ||
| var bottomController: NSHostingController<Bottom>? | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For SQL Server tables or schemas whose identifier contains a single quote (valid when bracket-quoted, e.g.
[O'Brien]),bracketedFullonly escapes]and is then interpolated into a quotedOBJECT_ID('...')literal. Opening the Triggers tab for such a table will at least fail with malformed SQL, and a malicious identifier can terminate the string and append extra statements; escape'in the full object-name literal before interpolation.Useful? React with 👍 / 👎.