Skip to content

feat(sidebar): browse stored procedures, functions and triggers with a source viewer - #2386

Merged
datlechin merged 2 commits into
mainfrom
feat/database-routines-triggers
Aug 23, 2026
Merged

feat(sidebar): browse stored procedures, functions and triggers with a source viewer#2386
datlechin merged 2 commits into
mainfrom
feat/database-routines-triggers

Conversation

@datlechin

Copy link
Copy Markdown
Member

Stored procedures, functions and triggers become browsable objects across every engine that has them, and selecting one opens its source in a read-only viewer.

Fixes #2383.

Root cause

The reporter asked for three sections and a viewer. Investigating why they were missing turned up three structural facts rather than one gap:

  1. Routines took the wrong extension road. Every other optional driver feature is a defaulted requirement on PluginDatabaseDriver plus a DriverPlugin capability static (fetchTriggers(table:schema:), supportsTriggers). Routines alone were an opt-in side protocol reached by as? PluginProcedureFunctionSupport, which returns [] on a miss. Exactly 2 of 22 drivers conformed, so 20 engines rendered "not implemented" identically to "none exist".
  2. The transfer types could not name the object. PluginRoutineInfo had no schema and no argument signature; PluginTriggerInfo had no owning table. A database-level trigger list was unrepresentable, and the adapter compensated by writing signature: routine.returnType.
  3. A trigger was not an object kind, and there was no viewer. SidebarObjectKind was six cases whose consumers were a hidden binary isRoutine ? routines : tables split. showRoutineDDL dumped DDL into an editable query tab, one Cmd+Return from running it, while the read-only viewer it should have used (DDLTextView) sat one folder away.

What changed

PluginKit, additive only. PluginRoutineInfo and PluginTriggerInfo gain the fields needed to address an object, each keeping its shipped initializer verbatim as @_disfavoredOverload (the PluginTableInfo precedent). Four new defaulted requirements on PluginDatabaseDriver: fetchRoutines(schema:), fetchRoutineDDL(_:), fetchAllTriggers(schema:), fetchTriggerDDL(_:). PluginProcedureFunctionSupport stays declared forever and is never removed; the default fetchRoutines adopts any driver that still conforms to it, so a plugin built against the old protocol keeps working untouched and the app only ever calls the new requirement.

One query where there were two. fetchProcedures and fetchFunctions are gone from the app-side driver protocol. The tree, the flat root and MCP each issued both; they now issue one catalog read per schema and split by kind.

Sidebar. SidebarObjectKind gains .trigger and a category that replaces the boolean isRoutine, so every dispatch site is a three-way switch the compiler checks. visible gains a declaredKinds term that can only add a section, never hide one that has rows, keeping the invariant recorded in that type's own doc comment.

Viewer. A new TabType.objectSource renders ObjectSourceView, extracted from the chrome TriggerDetailPane already had rather than built a second time. Read only, with Copy, Export (through the existing SQLFileService, replacing a hand-rolled NSSavePanel), Reload, and an explicit Open in Editor for when you do want to edit. It refetches on restore, so a reopened tab shows the current definition.

Engines. PostgreSQL, MySQL, MSSQL, Oracle, Dameng, Teradata, SQLite, LibSQL, Cloudflare D1, Cassandra, ClickHouse, DuckDB, Snowflake and BigQuery. Each engine's per-table and schema-wide trigger reads go through one query builder, so the Structure tab and the sidebar cannot disagree about a table's triggers.

Collateral defects fixed

All five were found while investigating, adversarially verified, and live inside the surface this rewrites:

  • PostgreSQL listing cross-joined pg_proc on name. Three overloads produced nine identical rows; because id's discriminator was the return type, all nine collapsed to one and two overloads were unreachable. Measured on PostgreSQL 17.11, before and after, below.
  • PostgreSQL DDL fetch was LIMIT 1 with no argument predicate, returning an arbitrary overload.
  • MySQL routine DDL dropped the schema, so SHOW CREATE PROCEDURE resolved against the session database. Browsing analytics and opening analytics.cleanup returned app.cleanup's body with nothing on screen saying so.
  • RoutineInfo.signature held the return type, rendered as an argument signature in four surfaces including the documented MCP list_routines contract. calculate_age(date) RETURNS integer copied as calculate_ageinteger.
  • The flat sidebar handed NSOutlineView the same node object at several row indices, because only the tree path deduplicated.

Two more engine bugs went with the rewrite: MySQL triggers lost their DEFINER, WHEN clause and ordering, and Oracle triggers were a CREATE OR REPLACE header with no body, because TRIGGER_BODY was never selected.

Measured, not assumed

scripts/check-postgres-object-queries.sh is committed so a future PostgreSQL version re-checks the hand-written catalog SQL instead of trusting the transcription. Against a local PostgreSQL 17.11 with three transform overloads, an aggregate and two same-named triggers on different tables:

$ scripts/check-postgres-object-queries.sh
PostgreSQL object catalog queries agree with 17.11 (Homebrew)

The old query on the same fixture:

 routine_name | data_type | external_language
--------------+-----------+-------------------
 transform    | integer   | SQL          <- nine identical rows for three overloads
 ... (9 total)

The new one returns five rows, three transform entries each with its own oid and argument list, and pg_get_functiondef addressed by oid returns the matching overload every time. The aggregate is excluded because pg_get_functiondef genuinely raises on one (ERROR: "my_sum" is an aggregate function), which would otherwise fail the whole listing.

Verification

Step Result
verify.sh build PASS
verify.sh test (15 suites) PASS, 125 cases
verify.sh lint TablePro Plugins TableProTests Packages PASS, 0 violations
Per-plugin compile (13 driver schemes) PASS
verify.sh abi <merge-base> diff is additive only, no symbol removed
scripts/check-postgres-object-queries.sh PASS against 17.11
shellcheck --severity=warning on the new script clean

verify.sh plugins (the AllPlugins aggregate) cannot run on this machine: the vendored oracle-nio fork's @TaskLocal macro fails to expand locally, which is a pre-existing toolchain issue unrelated to this change. Every driver plugin this PR touches was compiled individually through its own scheme instead. CI's AllPlugins step covers the aggregate.

Not done, and why

  • Trino, Redis and SurrealDB declare no routine support. Trino's SQL routines are catalog-dependent and SHOW FUNCTIONS FROM errors on connectors that cannot store them, which would show a failed Functions section on every schema. Redis functions are node-local libraries needing cluster fan-out and a RESP map parse. SurrealDB's INFO FOR DB returns a nested object the driver's result shape does not currently carry. Shipping a section that errors is worse than not offering one; each is a small follow-up.
  • Dameng and Teradata are written against their documented Oracle-compatible and DBC catalogs but not verified against a live server. Neither is reachable from here. The queries are in DamengPluginDriver+Routines.swift and TeradataObjectQueries.swift, and a failure surfaces as a visible error row rather than silence.
  • The docs screenshot is a placeholder. docs/images/routines-triggers-sidebar{,-dark}.png are neutral 1560x960 fills at the standard size so the page renders. A real capture needs a PostgreSQL connection created through the UI, which I could not drive in this environment. Please re-capture before the docs deploy.
  • No TableProUITests coverage. The flow needs a live server with routines in it; the bundled Chinook sample has neither procedures nor triggers, so there is nothing deterministic to drive.

Self-review

A code-review pass over the finished diff found fifteen defects, all fixed before this commit. The ones worth naming:

  • I had corrupted project.yml with an over-broad line sort, so xcodegen could not parse it. My earlier builds were silently using a stale generated project because I ran verify.sh generate with its output discarded and only checked the build. Restored and re-applied as four targeted lines, then every verification step above re-ran against a correctly generated project.
  • Five drivers were caching a routine's DDL in PluginRoutineInfo.identity, which RoutineInfo.id folds in. Editing the object made the viewer's resolve-by-id miss and serve stale text forever, and it wrote kilobytes of SQL into the tab-state JSON. identity is now addressing only; a new definition field carries the body.
  • PostgreSQLObjectQueries picked the pre-11 proisagg branch when the server version was unknown, and libpq reports 0 for a handle it has not connected. An unknown version now reads as modern.
  • Redshift, CockroachDB and PGlite have their own driver subclasses that implement none of this, so the capability flags I gave them would have shown sections that could never fill. Removed.
  • Oracle and Dameng filtered a schema-wide trigger browse on TABLE_OWNER, which is the schema of the table a trigger fires on, not the schema that owns it. Now OWNER for a schema browse and TABLE_OWNER for a per-table fetch.
  • Oracle built argument signatures with LISTAGG, which raises ORA-01489 past 4000 bytes and would fail the whole listing over one wide signature. Dropped: Oracle only overloads inside a package, so a standalone routine needs no signature to be identified.
  • The trigger fetch ran on every engine including Redis and DynamoDB. Gated on supportsDatabaseTriggerBrowse, which gates the query and never the display.
  • The viewer re-listed an entire schema on every open and every reload just to recover attributes. The ref carries them now.
  • WindowTitleResolver had no .objectSource arm, so the window titlebar read "SQL Query" while its tab read "Procedure: ...".
  • SidebarObjectListPresentation still decided emptiness from tables and routines only, hiding the whole tree for a schema whose only objects are triggers.
  • check-postgres-object-queries.sh had a grep -q under pipefail, so psql died of SIGPIPE and the guarded assertion silently never ran.

PluginKit ABI

Additive. New defaulted protocol requirements, new stored properties on two non-@frozen transfer structs with both shipped initializers kept byte-identical under @_disfavoredOverload, two new DriverPlugin statics with defaults, and three new public types. No published requirement was removed and no existing signature changed, so no currentPluginKitVersion bump and no plugin re-release.

Before / After

The change is visible in the sidebar (three new sections) and in what Show DDL opens (a read-only viewer rather than an editable query tab). I was not able to capture screenshots in this environment; see the docs placeholder note above.

@mintlify

mintlify Bot commented Aug 22, 2026

Copy link
Copy Markdown

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

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 22, 2026, 7:44 PM

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

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 04b67f2 into main Aug 23, 2026
8 checks passed
@datlechin
datlechin deleted the feat/database-routines-triggers branch August 23, 2026 02:51
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.

Support viewing Stored Procedures, Functions, and Triggers

1 participant