cockroachdb / cockroachdb/cockroach
[meta] plpgsql: implement EXECUTE for dynamic SQL
- 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's PL/pgSQL supports executing a dynamically-constructed
SQL string via the `EXECUTE` statement, optionally capturing rows
into variables and binding parameters:
```sql
CREATE PROCEDURE bump(tbl text, n int) AS $$
BEGIN
EXECUTE format('UPDATE %I SET counter = counter + $1', tbl)
USING n;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION fetch_one(tbl text) RETURNS some_row AS $$
DECLARE
r some_row;
BEGIN
EXECUTE format('SELECT * FROM %I LIMIT 1', tbl) INTO STRICT r;
RETURN r;
END;
$$ LANGUAGE plpgsql;
```
The PL/pgSQL parser successfully constructs an `*ast.DynamicExecute`
node, including parsing of the `INTO [STRICT] ` and
`USING ` clauses
([`pkg/sql/plpgsql/parser/lexer.go:200-258`](https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/plpgsql/parser/lexer.go#L200-L258)),
but the optbuilder has no `case *ast.DynamicExecute` arm
([`pkg/sql/opt/optbuilder/plpgsql.go:478-1314`](https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/opt/optbuilder/plpgsql.go#L478-L1314)),
so it falls into the `default:` arm and panics
`unsupportedPLStmtErr`. Today this surfaces under the catch-all
telemetry bucket `unimplemented.unimplemented PL/pgSQL statement`
(see #169557). The closed #123672 only improved the error wording —
no support was added.
This issue tracks the standalone-statement form. Two related dynamic-
SQL forms are tracked separately: `RETURN QUERY EXECUTE` (#169571)
and `OPEN cursor FOR EXECUTE` (#169574). All three are part of the
broader dynamic-SQL effort tracked by #115300.
The standalone-statement form is being implemented as a stack of
thin, independently-landable slivers, each tracked by its own
sub-issue:
- [ ] #172401 — basic `EXECUTE` in stored procedures (no `INTO`/
`USING`, no DDL/DCL, top-level statement only)
- [ ] #172403 — `USING` parameter binding
- [ ] #172402 — `INTO [STRICT]` clause for result capture
- [ ] #172404 — block dynamic `EXECUTE` under `SECURITY DEFINER`
- [ ] #172433 — allowlisted DDL/DCL via dynamic `EXECUTE`
- [ ] #172439 — support in UDFs and DO blocks
- [ ] #174682 — support in trigger functions
- [ ] #172491 — support inside control-flow blocks (IF/WHILE/loop/nested block)
- [ ] #172544 — count dynamic EXECUTE in routine statement metrics
**Describe the solution you'd like**
Add a `case *ast.DynamicExecute` arm to `buildPLpgSQLStatements`
that:
- Evaluates the query expression at runtime to a SQL string.
- Plans and binds the resulting statement using any `USING`
parameters ($1, $2, …).
- For non-`SELECT` statements, executes for side-effects (and
records the row count for a future `GET DIAGNOSTICS` integration —
see #117410).
- For `SELECT`-shaped statements with `INTO [STRICT] `,
assigns the first row to the target variables, applying the
same too-many-rows / no-rows handling as the static `SELECT INTO`
path (`STRICT` raises `no_data_found` / `too_many_rows`; without
`STRICT`, missing rows leave variables unset, extra rows are
silently ignored).
- Surfaces planning and execution errors through the routine's
normal exception path.
Note that as of today the parser at `lexer.go:222` discards the
`INTO` target's variable name (TODO comment there); part of this
work is to capture it properly so the optbuilder can perform the
assignment.
Epic CRDB-48117
Jira issue: CRDB-63545
Contributor guide
Assessment
This issue has not been assessed yet.