Skip to content

feat(structure): create, edit, and drop triggers across all engines#1708

Merged
datlechin merged 6 commits into
mainfrom
worktree-feat-triggers-v2-editing
Jun 18, 2026
Merged

feat(structure): create, edit, and drop triggers across all engines#1708
datlechin merged 6 commits into
mainfrom
worktree-feat-triggers-v2-editing

Conversation

@datlechin

Copy link
Copy Markdown
Member

PR 2 of the v2 trigger work (follows #1703). Adds create / edit / drop for triggers across all 7 supported engines, using the determined DDL-first design (research-backed, not a tool clone).

Design

  • DDL-first, lossless. The editor opens the trigger's real definition so every clause round-trips (avoids the regenerate-from-fields fidelity bug). For PostgreSQL it loads the function and the trigger (pg_get_functiondef + pg_get_triggerdef), since the logic lives in the function.
  • Correct per-engine apply. Edit uses CREATE OR REPLACE / CREATE OR ALTER where the engine supports it, else drop-and-recreate. Transactional-DDL engines (PostgreSQL, SQLite, SQL Server) wrap the change in a transaction; non-transactional engines (MySQL, Oracle) use a rollback buffer that restores the original on failure.
  • Routed through the app's safety pipeline. Every apply/drop goes through ExecutionGate (confirmation + safe-mode), records to query history, and broadcasts refreshData (the triggers tab auto-reloads).

Scope

  • PluginKit (additive, no ABI bump): createTriggerTemplate, fetchTriggerDefinition, generateDropTriggerSQL, triggerEditUsesReplace, supportsTransactionalDDL, and a supportsTriggerEditing capability.
  • Per-engine DDL for MySQL, MariaDB (via MySQL), PostgreSQL, SQLite, SQL Server, Oracle, libSQL, Cloudflare D1.
  • App layer: DatabaseDriver/adapter bridges, capability gating, the TriggerEditing orchestrator, the TriggerEditorView sheet (editable CodeEditSourceEditor), and a New/Edit/Delete action bar in the Triggers tab.
  • Tests for the adapter bridges; CHANGELOG + docs.

Engine notes

  • Oracle: create/drop fully; editing starts from the trigger header because OracleNIO can't read the trigger body (LONG / DBMS_METADATA would crash the connection).
  • Registry engines (SQL Server, Oracle, libSQL, Cloudflare D1) need their plugin releases to expose editing to users; bundled MySQL/MariaDB/PostgreSQL/SQLite ship with the app.

Status

Built in a worktree; not yet compiled (no Xcode here). App-target SwiftLint --strict is clean. Please build and report any errors — highest-risk spots are the TriggerEditorView editable SourceEditor and the confirmationDialog/sheet(item:) wiring in TriggerDetailView.

@mintlify

mintlify Bot commented Jun 17, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jun 17, 2026, 10:25 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d940c09d2b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

supportsDropIndex: driverType.supportsDropIndex,
supportsModifyPrimaryKey: driverType.supportsModifyPrimaryKey,
supportsTriggers: driverType.supportsTriggers,
supportsTriggerEditing: driverType.supportsTriggerEditing,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid calling the new plugin requirement on stale binaries

This is a new DriverPlugin requirement, but the registry is reading it directly from every loaded driver type. Existing downloadable/third-party driver plugins compiled against the previous PluginKit will not have a witness-table entry for this requirement; the nearby supportsColumnReorder comment already documents that reading newly added requirements from driverType can crash stale plugins. In an install with any older external driver plugin, registration can fail/crash before the app can even fall back to the default false capability, so this needs the same compatibility treatment as the preserved snapshot fields.

Useful? React with 👍 / 👎.

static let parameterStyle: ParameterStyle = .dollar
static let supportsDropDatabase = true
static let supportsTriggers = true
static let supportsTriggerEditing = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not enable trigger editing for PostgreSQL aliases

Because this plugin also advertises additionalDatabaseTypeIds for Redshift and CockroachDB, and DatabaseConnection.supportsTriggerEditing resolves aliases through the PostgreSQL plugin type, this single flag exposes the New/Edit/Delete trigger UI for those variants too. The Redshift and Cockroach driver classes do not implement the trigger create/fetch/drop hooks, so those connections fall back to generic trigger DDL (or empty trigger lists) and the new actions can only fail or run inappropriate PostgreSQL-oriented SQL; gate this capability per variant instead of enabling it on the shared plugin descriptor.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1809fe30aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +60 to +61
sql: sql,
kind: .schemaMutation,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Authorize generated trigger drops

When editing on non-replace drivers such as MySQL, SQLite/libSQL, or D1, the code authorizes only the user-supplied CREATE text, but then lines 78 and 82-85 execute a generated DROP TRIGGER before it. Because DefaultExecutionGate treats destructive SQL as always confirmation-worthy even in Silent mode, these edit flows can drop an existing trigger without the destructive confirmation or audit coverage that schema changes with DROP normally get; include the generated drop in the authorized SQL/kind before executing it.

Useful? React with 👍 / 👎.


static let isDownloadable = true
static let supportsTriggers = true
static let supportsTriggerEditing = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't enable Oracle editing without full trigger DDL

For Oracle connections, enabling the edit UI here exposes Edit/Save for existing triggers, but OraclePluginDriver does not override fetchTriggerDefinition and its fetchTriggers query builds statement from only TRIGGER_NAME, type, event, status, and WHEN clause, omitting the trigger body. As a result, editing an existing Oracle trigger opens and applies an incomplete CREATE OR REPLACE TRIGGER header rather than the real definition, so saves either fail or replace the trigger incorrectly; keep this disabled until the driver returns full trigger DDL.

Useful? React with 👍 / 👎.

return triggers
}

var triggerEditUsesReplace: Bool { true }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate PostgreSQL replace syntax by server version

This advertises replace-based trigger editing for every PostgreSQL server, and the editor later emits CREATE OR REPLACE TRIGGER; PostgreSQL 13 and older do not accept that syntax, while this driver already supports/version-gates much older server versions via serverVersionNumber. On those connections the new Create/Edit Trigger UI will consistently fail instead of using a drop-and-create fallback or hiding editing.

Useful? React with 👍 / 👎.

func createTriggerTemplate(table: String, schema: String?) -> String? {
let resolved = schema ?? _currentSchema
return """
CREATE OR ALTER TRIGGER \(quoteIdentifier("trigger_name"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid CREATE OR ALTER on older SQL Server

The new SQL Server trigger template unconditionally uses CREATE OR ALTER TRIGGER, and fetched definitions are rewritten to the same syntax, but older SQL Server versions that the driver can connect to reject CREATE OR ALTER. The existing view template already checks MSSQLCapabilities before using this syntax, so trigger editing should do the same or fall back to drop/create; otherwise New/Edit Trigger fails on those servers even though the UI is enabled.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40675990bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
}

var supportsTransactionalDDL: Bool { true }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid forcing transactions for remote libSQL triggers

On libSQL/Turso connections using the default remote mode, this flag makes TriggerApplyStrategy choose the transactional path for both trigger creates and SQLite-style edits, so TriggerEditing.runInTransaction calls beginTransaction() before running the DDL. I checked LibSQLPlugin.swift and libsqlMode defaults to remote, while executeTransactionStatement throws 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 👍 / 👎.

@datlechin datlechin merged commit b27c6fa into main Jun 18, 2026
3 of 4 checks passed
@datlechin datlechin deleted the worktree-feat-triggers-v2-editing branch June 18, 2026 15:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4306cf6066

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


static let supportsDropDatabase = true
static let supportsTriggers = true
static let supportsTriggerEditing = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fetch full MySQL trigger DDL before enabling edits

Enabling editing here makes MySQL/MariaDB edits use TriggerDetailView.editTrigger's fallback because MySQLPluginDriver does not implement fetchTriggerDefinition; that fallback TriggerInfo.statement is reconstructed from information_schema.TRIGGERS.ACTION_STATEMENT and omits full SHOW CREATE TRIGGER metadata such as the original DEFINER. Since the apply path drops and recreates on edit, even a no-op save can change the trigger to run under the current user (and rollback restores the same lossy definition), so fetch the full trigger DDL before advertising editing.

Useful? React with 👍 / 👎.

let fetched = try? await driver?.fetchTriggerDefinition(name: trigger.name, table: tableName)
let sql = (fetched ?? nil) ?? trigger.statement
editorSheet = TriggerEditorSheetItem(
mode: .edit(originalName: trigger.name, originalDefinition: trigger.statement),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve disabled trigger state during edits

When the selected SQL Server trigger is disabled (TriggerInfo.enabled == false), this edit mode drops that state: only the name and definition are carried into the sheet, and the SQL Server driver rewrites the definition to CREATE OR ALTER TRIGGER. Running ALTER TRIGGER/CREATE OR ALTER TRIGGER enables disabled triggers, so saving a disabled trigger, even without semantic changes, makes it fire again; carry the enabled flag through and re-disable after apply or block editing disabled triggers.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant