cockroachdb / cockroachdb/cockroach

sql: DDL in a routine can drop a table the calling statement is using

Open
#175,642 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-sql-pgcompat A-sql-plpgsql A-sql-routine C-bug O-agent T-sql-foundations
Dominant language
Go
Stars
32.5k
Forks
4.1k
PR merge metrics
PR metrics pending

Description

Describe the problem

Procedure bodies may contain CREATE TABLE / DROP TABLE (with
sql.procedures.plpgsql.late_binding.enabled). A UDF can CALL such a
procedure, and the UDF can be invoked from inside another statement. Nothing
stops the routine from dropping the very table the outer statement is reading
or writing. The outer statement then completes as if nothing happened.

PostgreSQL rejects this at the DDL with 55006:

ERROR:  cannot DROP TABLE "pc" because it is being used by active queries in this session

The same guard (CheckTableNotInUse) also covers ALTER TABLE, open cursors,
and suspended portals. CockroachDB has a blanket guard for cursors (#74608) and
none for portals (#99085). This issue is the third gap: the active statement
itself.

To Reproduce

SET CLUSTER SETTING sql.procedures.plpgsql.late_binding.enabled = true;
CREATE TABLE parent (p INT PRIMARY KEY);
CREATE TABLE child (c INT PRIMARY KEY, p INT REFERENCES parent(p));
INSERT INTO parent VALUES (1);

CREATE PROCEDURE drop_child() LANGUAGE PLpgSQL AS $$
BEGIN
  DROP TABLE child;
END $$;

CREATE FUNCTION f() RETURNS INT LANGUAGE PLpgSQL AS $$
BEGIN
  CALL drop_child();
  RETURN 1;
END $$;

INSERT INTO child VALUES (1, f());

The INSERT succeeds. child is gone afterwards. Same result with the legacy
and declarative schema changers.

With defer_foreign_key_checks = true the deferred FK check for the INSERT
is planned at commit against a dropped table and fails with an internal error
instead.

Expected behavior

The DROP TABLE inside the routine fails with 55006, as in PostgreSQL, and
the outer statement fails with it.

Additional context

ALTER TABLE is not reachable this way today because procedure bodies and
EXECUTE reject it. DROP TABLE of an unrelated table, or CREATE TABLE,
from inside a statement's input is fine in PostgreSQL and should stay allowed.

A precise fix needs the routine DDL path to know which relations the active
statement depends on. A conservative one rejects any DROP TABLE (and future
ALTER TABLE) from a routine that is not the top-level CALL.

Related: #174683 (DDL in UDFs) lists mid-query safety as an open requirement.

Jira issue: CRDB-68542

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the routine DDL path described in the issue and reproduce the SQL example with late binding enabled. Trace how the active statement's relation dependencies are exposed to routine execution, including the legacy and declarative schema changers. Done means dropping a relation used by the outer statement fails with 55006, while unrelated DROP TABLE and CREATE TABLE remain allowed.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.