cockroachdb / cockroachdb/cockroach

sql/schemachanger: cannot plan two statements that replace the same element in one transaction

Open
#175,594 1 comment 0 reactions 0 assignees View on GitHub
A-schema-changer-impl A-schema-transactional branch-master branch-release-25.2 branch-release-25.4 branch-release-26.2 branch-release-26.3 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**

Several declarative schema changer builders express "change this setting" as: drop the element holding the current value, add an element holding the new value, and pair the two with a `SameStagePrecedence` rule so nothing observes the descriptor between the two writes.

That works for a single statement. It breaks when a second statement in the same transaction changes the same setting again. The element the first statement added is dropped by the second, so it is `ABSENT` at the start of the transaction and `ABSENT` at its end — it never transitions, and the stage builder never schedules it. The dependency rule still matches it and demands it share a stage with the new element's transition to `PUBLIC`, which cannot be arranged.

**To Reproduce**

Row-level TTL:

```sql
SET autocommit_before_ddl = false;
SET create_table_with_schema_locked = 'off';
SET use_declarative_schema_changer = 'unsafe_always';
CREATE TABLE t (i INT PRIMARY KEY);
BEGIN;
ALTER TABLE t SET (ttl_expire_after = '1 hour');
ALTER TABLE t SET (ttl_expire_after = '2 hours');
COMMIT;
```

```
ERROR: internal error: building declarative schema changer plan in StatementPhase (rollback=false) for ALTER TABLE; ALTER TABLE: failed to satisfy [[RowLevelTTL:{DescID: 106, SeqNum: 0}, ABSENT], ABSENT] -dep-SameStagePrecedence-> [[RowLevelTTL:{DescID: 106, SeqNum: 1}, PUBLIC], PUBLIC] rule "old TTL params are dropped before the new one is added"
```

Index visibility, same session settings:

```sql
CREATE TABLE t (i INT PRIMARY KEY, j INT);
CREATE INDEX idx ON t (j);
BEGIN;
ALTER INDEX t@idx VISIBILITY 0.5;
ALTER INDEX t@idx VISIBILITY 0.7;
COMMIT;
```

```
ERROR: internal error: ... failed to satisfy [[IndexNotVisible:{DescID: 106, IndexID: 2, Value: 0.5}, ABSENT], ABSENT] -dep-SameStagePrecedence-> [[IndexNotVisible:{DescID: 106, IndexID: 2, Value: 0.3}, PUBLIC], PUBLIC] rule "old index invisibility is dropped before the new one is added"
```

`IndexNotVisible` carries no `SeqNum` — its key holds the value — so this is not about sequence numbering.

**Expected behavior**

The second statement plans, and the transaction behaves as if only the final value had been set. The intermediate element never reaches the committed catalog, so there is nothing for the same-stage rule to protect.

**Why this hasn't been hit**

Both cases are masked at the default `use_declarative_schema_changer` setting. TTL is rejected earlier by a dedicated guard (`cannot modify TTL settings while another schema change on the table is being processed`, SQLSTATE 0A000) — the same missing capability wearing a friendlier error. Index visibility succeeds at the default setting and fails only when the declarative changer is forced.

**Why it matters now**

`ALTER DEFAULT PRIVILEGES` is being converted to the declarative changer (#175591) so that a procedure body mixing it with other DDL runs in a single changer. A procedure body is one transaction, and one that changes the same role's defaults twice hits exactly this — with no guard in front of it. The general planner limitation needs solving before that conversion can be relied on inside stored procedures.

**Possible directions**

- Relax the stage builder so a dependency edge whose source is absent at both ends of the transaction is vacuously satisfied. Note that the plan validator enforces the same-stage constraint independently, so it needs a matching carve-out — a stage-builder-only change still trips the validator.
- Have builders avoid materializing an intermediate element at all when the element being replaced was created by an earlier statement of the same transaction.

**Environment:** master (26.4 dev)

Jira issue: CRDB-68482

Epic CRDB-65937

Contributor guide

Open the contributing guide

Research direction

Start with the declarative schema changer reproduction using row-level TTL or index visibility and trace the SameStagePrecedence handling in the stage builder and plan validator. Compare elements absent at both transaction boundaries with intermediate replacements, then determine how both planner stages should treat them. Done means both statements plan and commit with only the final value represented, without violating validation.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sql
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.