cockroachdb / cockroachdb/cockroach

sql/plpgsql: support dynamic EXECUTE in trigger functions

Open
#174,682 1 comment 0 reactions 0 assignees View on GitHub
A-sql-pgcompat A-sql-plpgsql A-sql-routine A-sql-trigger C-enhancement O-agent T-sql-foundations
Dominant language
Go
Stars
32.5k
Forks
4.1k
PR merge metrics
PR metrics pending

Description

**Is your feature request related to a problem? Please describe.**
PostgreSQL places no restriction on `EXECUTE` in a PL/pgSQL trigger
function — a trigger function is an ordinary PL/pgSQL function returning
`trigger`, and dynamic SQL behaves there as it does anywhere else. It is
the basis of the common generic-audit and row-routing trigger patterns:

```sql
CREATE FUNCTION audit_trg() RETURNS trigger AS $$
BEGIN
EXECUTE format('INSERT INTO %I SELECT ($1).*', TG_TABLE_NAME || '_audit')
USING NEW;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
```

CockroachDB rejects this. After #172439, trigger functions are the only
PL/pgSQL routine kind where dynamic `EXECUTE` is still blocked — stored
procedures, UDFs, and DO blocks are all supported — and they fail with a
dedicated `unimplemented` error pointing at #169581.

**Describe the solution you'd like**
Allow `*ast.DynamicExecute` in a trigger function body, subject to the
same statement allowlist and the same `SECURITY DEFINER` restriction that
apply to every other routine kind. Points to work out:

- **`USING NEW` / `USING OLD`.** These are the interesting arguments to
pass, and they are records. `USING` binding substitutes each `$n` with a
cast of the argument to the `USING` expression's type, so tuple-typed
arguments have to survive that path, and `($1).*` has to work in the
command string. Worth verifying rather than assuming it comes for free.
- **Trigger return handling.** A trigger function must return a row or
`NULL`. The rest-of-body build after a dynamic `EXECUTE` is deferred to
execution time, so a trigger function that falls off the end after an
`EXECUTE` would raise at runtime rather than at `CREATE` time, as a UDF
does today. Confirm the trigger-specific return validation composes with
the deferred tail.
- **Trigger-function build caching.** Trigger function bodies are built
and cached per trigger during optbuilding
(`pkg/sql/opt/optbuilder/trigger.go`), while the dynamic-`EXECUTE` body
builder rebuilds its statement on each invocation rather than reusing a
cached plan. Confirm the two do not interact badly.
- **Privileges.** Trigger functions honor their declared security mode, so
a `SECURITY DEFINER` trigger function is already covered by the runtime
elevated-privileges gate and the `CREATE`-time check from #172404. No new
rule is needed, but it needs test coverage.

**Describe alternatives you've considered**
Leaving trigger functions blocked. The block is an implementation gap, not
a deliberate semantic divergence: triggers run as the invoking user unless
declared `SECURITY DEFINER`, so nothing about the trigger context makes
dynamic SQL more dangerous than it is in a UDF. Keeping it would leave a
parity gap in what is arguably the most common dynamic-SQL use case.

**Additional context**
Part of the dynamic-SQL EXECUTE effort tracked in #169581. Builds on
#172439, which lifted the restriction for UDFs and DO blocks and left
trigger functions as the remaining unsupported kind.

Epic CRDB-48117

Jira issue: CRDB-67883

Contributor guide

Open the contributing guide

Research direction

Start with trigger-function building and caching in pkg/sql/opt/optbuilder/trigger.go, then compare the dynamic-EXECUTE support added for UDFs and DO blocks in #172439. Verify USING NEW/OLD records, deferred trigger return validation, per-invocation rebuilding, and SECURITY DEFINER behavior; done means trigger functions accept dynamic EXECUTE with coverage for these cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sql
Domain
databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.