SequelPG v0.1.13: run functions and procedures, full CREATE TABLE definitions, and syntax-highlighted DDL
v0.1.12 was about the grid — rebuilding the rendering layer on AppKit, decoding every PostgreSQL type from the binary wire format, and finally wiring up Test Connection. v0.1.13 is about turning the navigator into something you can actually do things from. Every selectable database object lights up a tab; functions and procedures used to be the exception — you could look at their definition, but to invoke one you had to retype the signature into the SQL editor, guess at literal syntax for each argument type, and chase the cast errors PG returned. This release fixes that with a focused Run / Call sheet, reconstructs full CREATE TABLE statements in the Definition tab, and gives every entity’s DDL the same syntax highlighting as the SQL editor.
Run / Call sheet for functions and procedures
Right-click any function or procedure in the navigator (or use the new Run… button on its Definition tab) and a focused builder opens. The header shows the qualified signature and return type. Below it, one row per input parameter: name, mode badge for OUT / INOUT / VARIADIC where relevant, declared type, a value field, and a mode picker per argument. The mode picker is the small detail that does most of the work:
- Value — the input is quote-escaped and cast to the declared type. Without the cast,
'42'could resolve to a different overload than you intended, so SequelPG appends::typefor every non-text-like type. - Expression — the input is inserted verbatim. Use it for
now(),gen_random_uuid(),ARRAY[1, 2, 3], or any other SQL expression. The field switches to a monospaced font so it’s obvious which arguments are literals and which are computed. Expressions pass through a conservative safety check that rejects multi-statement and DDL keywords; anything stricter can drop into the Query tab via the Edit in Query escape hatch. - NULL — emits the keyword
NULL. A common UX trap with database GUIs is that an empty text field becomes the empty string, which silently differs fromNULL; the explicit mode toggle keeps the distinction visible. - DEFAULT — omits the argument from the call entirely, so PostgreSQL applies the declared default. This mode is only offered for parameters that have a default; it starts pre-selected for them, so a function like
recent_orders(p_limit integer DEFAULT 10)works with zero typing.
When every input parameter has a name, the generated SQL uses named-argument notation: fn(amount => 42, memo => 'hello'). That is worth the few extra characters because it lets the sheet skip defaulted arguments without breaking positional ordering, and it makes the audit trail in Query History readable months later. Anonymous-arg functions fall back to positional syntax. A Show SQL toggle reveals the live, regenerating preview if you want to see exactly what will be executed.
The result panel reshapes itself to fit what PG returns:
- A scalar return (single row, single column) renders as a large monospaced value, with the column name above it. Useful for utility functions that return a count or a computed value.
- A set-returning function —
SETOF foo,RETURNS TABLE(...)— renders in the same AppKit results grid the Query tab uses, so column type and row count are visible at a glance. - A procedure call (
CALL ...) shows a “Procedure completed” banner plus the execution time; the button label changes from Run to Call so it’s clear which statement is about to be issued. - Trigger functions and aggregates still appear in the navigator, but the sheet refuses to build a call for them and explains why (trigger functions are invoked by triggers, aggregates take a column expression). Better to tell you up front than to have PG reject the call.
How the metadata is resolved
Overloaded functions are the reason this is harder than it looks. The navigator stores each function as name(argtypes); SequelPG splits that signature back out and runs it through regprocedure so the lookup pins down a single pg_proc OID rather than guessing based on name alone. From that OID we read proargnames, proargmodes, proallargtypes (falling back to proargtypes when the function has only IN params), proretset, and prorettype, then format each type via pg_catalog.format_type.
Two compatibility footnotes worth recording here, in case anyone is doing similar work against the catalog:
proargtypesis anoidvector, an internal type with 0-based array indexing. Casting it tooid[]preserves the lower bound; PostgresNIO’s array decoder rejects any array whose lower bound isn’t 1, which made the metadata silently come back empty.unnest WITH ORDINALITY+array_aggre-aggregates the array as a normal 1-basedint[].proargnamescan containNULLelements when some parameters are anonymous. PostgresNIO won’t decode an array of optionals through the normal path, so we coalesce nulls to empty strings server-side and treat empty strings as “no name” downstream.
Full CREATE TABLE in the Definition tab
Previously, opening the Definition tab for a table showed “Use the Structure tab to view table details” — useful enough for browsing, but useless when you wanted to copy a table’s shape to another database. v0.1.13 reconstructs the full statement from pg_catalog in one SQL round-trip:
- Columns with their type, default expression, identity (
GENERATED ALWAYS AS IDENTITY/GENERATED BY DEFAULT AS IDENTITY), computed columns (GENERATED ALWAYS AS (...) STORED), and NOT NULL flags. - Table-level constraints in canonical order — primary key, unique, foreign keys, check, exclude — each with
pg_get_constraintdefoutput. PARTITION BYclause for partitioned parents (relkind = ‘p’).- Separate
CREATE INDEXstatements for every secondary index, skipping the ones that already back a constraint (so the unique index behind aUNIQUEconstraint isn’t double-emitted). COMMENT ON TABLEandCOMMENT ON COLUMNfor any object with a non-null description.
PG17 changed how NOT NULL is stored — named constraints with contype = ‘n’ showed up in pg_constraint in addition to pg_attribute.attnotnull. Naively pulling every constraint row produced redundant CONSTRAINT … NOT NULL col lines underneath the per-column NOT NULL already in the column list, so we now filter contype <> ‘n’. The same query runs unchanged on PG14 through PG18, since the filter is a no-op on older versions that don’t use that contype.
Syntax-highlighted Definition tab for every entity
The Definition tab used to render DDL as plain monospaced text. Functions, views, types, and now tables all benefit from the same tokenizer the SQL editor uses — keywords, string literals, comments, numbers, and operators all get their own color. The read-only renderer is a thin NSViewRepresentable wrapping NSTextView + the existing SQLTextStorage, so a future theme change updates both the editor and the viewer in one place.
Navigator tree fixes
Two long-standing annoyances in the sidebar:
The tree collapsed when you clicked a leaf row. Expand demo → app → Functions, click a function in the list, and every parent DisclosureGroup would snap shut. The cause: the List’s selection binding was writing to the view-model through an async Task, so on its very next read the binding’s getter returned the old selection. SwiftUI saw the apparent rollback and re-rendered the rows, which had the side effect of collapsing parents. Applying the selection synchronously in the binding setter (and deferring the catalog queries to a Task afterward) fixes it.
Disclosure labels weren’t clickable. On macOS sidebar lists, DisclosureGroup only toggles on the chevron by default — you couldn’t click the database, schema, or category label itself to expand. Every non-leaf label is now wrapped in .contentShape(Rectangle()) + .onTapGesture so clicking the text expands or collapses just like the arrow does.
What’s next
The Run sheet covers the everyday case: invoking an existing routine to see what it does. The remaining function-shaped work is symmetric — defining them:
- A richer Create Function sheet with typed parameter rows, a return-type picker (including
RETURNS TABLE(...)), and toggles forSECURITY DEFINER/STRICT/PARALLEL/VOLATILE/STABLE/IMMUTABLE. - Procedure-specific create with arg-mode pickers (
IN/INOUT/OUT/VARIADIC) and a transaction-aware body template. - Edit-in-place for existing routines, so the Definition tab isn’t a read-only dead end.
- Foreign-key cell navigation in the results grid — click an FK value, jump to the referenced row.
SequelPG is open source on GitHub. Grab v0.1.13 from the releases page, file issues, or send a PR. Feedback is always welcome.