-
-
Notifications
You must be signed in to change notification settings - Fork 297
feat(structure): create, edit, and drop triggers across all engines #1708
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
b7ee78a
d940c09
1809fe3
0b8937e
4067599
4306cf6
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 |
|---|---|---|
|
|
@@ -104,9 +104,9 @@ | |
| extra: isIdentity ? "IDENTITY" : nil | ||
| ) | ||
| } | ||
| identityCacheLock.lock() | ||
|
Check warning on line 107 in Plugins/MSSQLDriverPlugin/MSSQLPluginDriver+Schema.swift
|
||
| identityColumnsByTable[table] = identityColumns | ||
| identityCacheLock.unlock() | ||
|
Check warning on line 109 in Plugins/MSSQLDriverPlugin/MSSQLPluginDriver+Schema.swift
|
||
| return columns | ||
| } | ||
|
|
||
|
|
@@ -243,6 +243,41 @@ | |
| } | ||
| } | ||
|
|
||
| var triggerEditUsesReplace: Bool { true } | ||
|
|
||
| var supportsTransactionalDDL: Bool { true } | ||
|
|
||
| func createTriggerTemplate(table: String, schema: String?) -> String? { | ||
| let resolved = schema ?? _currentSchema | ||
| return """ | ||
| CREATE OR ALTER TRIGGER \(quoteIdentifier("trigger_name")) | ||
|
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.
The new SQL Server trigger template unconditionally uses Useful? React with 👍 / 👎. |
||
| ON \(quoteIdentifier(resolved)).\(quoteIdentifier(table)) | ||
| AFTER INSERT | ||
| AS | ||
| BEGIN | ||
| SET NOCOUNT ON; | ||
| -- INSERT INTO audit (...) SELECT ... FROM inserted; | ||
| END | ||
| """ | ||
| } | ||
|
|
||
| func fetchTriggerDefinition(name: String, table: String, schema: String?) async throws -> String? { | ||
| let esc = (schema ?? _currentSchema).replacingOccurrences(of: "]", with: "]]") | ||
| let bracketedName = name.replacingOccurrences(of: "]", with: "]]") | ||
| let sql = "SELECT OBJECT_DEFINITION(OBJECT_ID('[\(esc)].[\(bracketedName)]'))" | ||
| let result = try await execute(query: sql) | ||
| guard let definition = result.rows.first?[safe: 0]?.asText, !definition.isEmpty else { return nil } | ||
| guard let range = definition.range(of: "CREATE TRIGGER", options: .caseInsensitive) else { | ||
| return definition | ||
| } | ||
| return definition.replacingCharacters(in: range, with: "CREATE OR ALTER TRIGGER") | ||
| } | ||
|
|
||
| func generateDropTriggerSQL(name: String, table: String, schema: String?) -> String? { | ||
| let resolved = schema ?? _currentSchema | ||
| return "DROP TRIGGER \(quoteIdentifier(resolved)).\(quoteIdentifier(name))" | ||
| } | ||
|
|
||
| func fetchAllColumns(schema: String?) async throws -> [String: [PluginColumnInfo]] { | ||
| let esc = effectiveSchemaEscaped(schema) | ||
| let sql = """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -147,6 +147,7 @@ final class MySQLPlugin: NSObject, TableProPlugin, DriverPlugin { | |
|
|
||
| static let supportsDropDatabase = true | ||
| static let supportsTriggers = true | ||
| static let supportsTriggerEditing = true | ||
|
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.
Enabling editing here makes MySQL/MariaDB edits use Useful? React with 👍 / 👎. |
||
|
|
||
| func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver { | ||
| MySQLPluginDriver(config: config) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin, PluginDiagnost | |
|
|
||
| static let isDownloadable = true | ||
| static let supportsTriggers = true | ||
| static let supportsTriggerEditing = true | ||
|
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, enabling the edit UI here exposes Edit/Save for existing triggers, but Useful? React with 👍 / 👎. |
||
| static let pathFieldRole: PathFieldRole = .serviceName | ||
| static let supportsForeignKeyDisable = false | ||
| static let supportsSchemaSwitching = true | ||
|
|
@@ -512,6 +513,25 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable { | |
| } | ||
| } | ||
|
|
||
| var triggerEditUsesReplace: Bool { true } | ||
|
|
||
| func createTriggerTemplate(table: String, schema: String?) -> String? { | ||
| let quotedTable = "\"\(table.replacingOccurrences(of: "\"", with: "\"\""))\"" | ||
| return """ | ||
| CREATE OR REPLACE TRIGGER \("\"TRIGGER_NAME\"") | ||
| BEFORE INSERT ON \(quotedTable) | ||
| FOR EACH ROW | ||
| BEGIN | ||
| -- :NEW.column := ...; | ||
| NULL; | ||
| END; | ||
| """ | ||
| } | ||
|
|
||
| func generateDropTriggerSQL(name: String, table: String, schema: String?) -> String? { | ||
| "DROP TRIGGER \"\(name.replacingOccurrences(of: "\"", with: "\"\""))\"" | ||
| } | ||
|
|
||
| func fetchAllColumns(schema: String?) async throws -> [String: [PluginColumnInfo]] { | ||
| let escaped = effectiveSchemaEscaped(schema) | ||
| let sql = """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -125,6 +125,7 @@ final class PostgreSQLPlugin: NSObject, TableProPlugin, DriverPlugin { | |
| static let parameterStyle: ParameterStyle = .dollar | ||
| static let supportsDropDatabase = true | ||
| static let supportsTriggers = true | ||
| static let supportsTriggerEditing = true | ||
|
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.
Because this plugin also advertises Useful? React with 👍 / 👎. |
||
|
|
||
| static let sqlDialect: SQLDialectDescriptor? = SQLDialectDescriptor( | ||
| identifierQuote: "\"", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -337,6 +337,70 @@ final class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { | |
| return triggers | ||
| } | ||
|
|
||
| var triggerEditUsesReplace: Bool { true } | ||
|
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.
This advertises replace-based trigger editing for every PostgreSQL server, and the editor later emits Useful? React with 👍 / 👎. |
||
|
|
||
| var supportsTransactionalDDL: Bool { true } | ||
|
|
||
| private func qualifiedTable(_ table: String, schema: String?) -> String { | ||
| let resolved = schema ?? core.currentSchema | ||
| return "\(quoteIdentifier(resolved)).\(quoteIdentifier(table))" | ||
| } | ||
|
|
||
| func createTriggerTemplate(table: String, schema: String?) -> String? { | ||
| let qualified = qualifiedTable(table, schema: schema) | ||
| let fn = qualifiedTable("trigger_function", schema: schema) | ||
| return """ | ||
| CREATE OR REPLACE FUNCTION \(fn)() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| AS $function$ | ||
| BEGIN | ||
| -- NEW.updated_at := now(); | ||
| RETURN NEW; | ||
| END; | ||
| $function$; | ||
|
|
||
| CREATE OR REPLACE TRIGGER \(quoteIdentifier("trigger_name")) | ||
| BEFORE INSERT ON \(qualified) | ||
| FOR EACH ROW | ||
| EXECUTE FUNCTION \(fn)(); | ||
| """ | ||
| } | ||
|
|
||
| func fetchTriggerDefinition(name: String, table: String, schema: String?) async throws -> String? { | ||
| let resolvedSchema = schema ?? core.currentSchema | ||
| let query = """ | ||
| SELECT pg_get_functiondef(t.tgfoid), pg_get_triggerdef(t.oid) | ||
| FROM pg_catalog.pg_trigger t | ||
| JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid | ||
| JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace | ||
| WHERE t.tgname = '\(escapeLiteral(name))' | ||
| AND c.relname = '\(escapeLiteral(table))' | ||
| AND n.nspname = '\(escapeLiteral(resolvedSchema))' | ||
| AND NOT t.tgisinternal | ||
| LIMIT 1 | ||
| """ | ||
| let result = try await execute(query: query) | ||
| guard let row = result.rows.first, row.count >= 2, | ||
| let functionDef = row[0].asText, | ||
| let triggerDef = row[1].asText else { return nil } | ||
| let editableTrigger: String | ||
| if triggerDef.range(of: "CREATE CONSTRAINT TRIGGER", options: .caseInsensitive) != nil { | ||
| let drop = generateDropTriggerSQL(name: name, table: table, schema: schema) ?? "" | ||
| editableTrigger = "\(drop);\n\(triggerDef)" | ||
| } else { | ||
| editableTrigger = triggerDef.replacingOccurrences( | ||
| of: "CREATE TRIGGER ", | ||
| with: "CREATE OR REPLACE TRIGGER " | ||
| ) | ||
| } | ||
| return "\(functionDef);\n\n\(editableTrigger);" | ||
| } | ||
|
|
||
| func generateDropTriggerSQL(name: String, table: String, schema: String?) -> String? { | ||
| "DROP TRIGGER IF EXISTS \(quoteIdentifier(name)) ON \(qualifiedTable(table, schema: schema))" | ||
| } | ||
|
|
||
| func fetchAllForeignKeys(schema: String?) async throws -> [String: [PluginForeignKeyInfo]] { | ||
| let schemaLiteral = escapeLiteral(schema ?? core.currentSchema) | ||
| let query = """ | ||
|
|
||
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.
On libSQL/Turso connections using the default remote mode, this flag makes
TriggerApplyStrategychoose the transactional path for both trigger creates and SQLite-style edits, soTriggerEditing.runInTransactioncallsbeginTransaction()before running the DDL. I checkedLibSQLPlugin.swiftandlibsqlModedefaults toremote, whileexecuteTransactionStatementthrows unless the backend is.local, so New/Edit Trigger always fails on the main remote/Turso path before the trigger SQL is executed; make this capability backend-dependent or avoid the transaction wrapper for remote libSQL.Useful? React with 👍 / 👎.