diff --git a/pkg/sql/schemachanger/rel/schema_rules.go b/pkg/sql/schemachanger/rel/schema_rules.go index 0fb70a0764e8..cd2edb1b96de 100644 --- a/pkg/sql/schemachanger/rel/schema_rules.go +++ b/pkg/sql/schemachanger/rel/schema_rules.go @@ -80,21 +80,26 @@ func (sc *Schema) DefNotJoin1(name string, a Var, def func(a Var) Clauses) Rule1 return sc.rule(name, notJoin, def, a).(Rule1) } -// DefNotJoin2 defines a not-join rule with two bound variable arguments. -func (sc *Schema) DefNotJoin2(name string, a, b Var, def func(a, b Var) Clauses) Rule2 { - return sc.rule(name, notJoin, def, a, b).(Rule2) -} - // Def2 defines a Rule2. func (sc *Schema) Def2(name string, a, b Var, def func(a, b Var) Clauses) Rule2 { return sc.rule(name, regular, def, a, b).(Rule2) } +// DefNotJoin2 defines a not-join rule with two bound variable arguments. +func (sc *Schema) DefNotJoin2(name string, a, b Var, def func(a, b Var) Clauses) Rule2 { + return sc.rule(name, notJoin, def, a, b).(Rule2) +} + // Def3 defines a Rule3. func (sc *Schema) Def3(name string, a, b, c Var, def func(a, b, c Var) Clauses) Rule3 { return sc.rule(name, regular, def, a, b, c).(Rule3) } +// DefNotJoin3 defines a not-join rule with three bound variable arguments. +func (sc *Schema) DefNotJoin3(name string, a, b, c Var, def func(a, b, c Var) Clauses) Rule3 { + return sc.rule(name, notJoin, def, a, b, c).(Rule3) +} + // Def4 defines a Rule4. func (sc *Schema) Def4(name string, a, b, c, d Var, def func(a, b, c, d Var) Clauses) Rule4 { return sc.rule(name, regular, def, a, b, c, d).(Rule4) diff --git a/pkg/sql/schemachanger/scplan/internal/opgen/register.go b/pkg/sql/schemachanger/scplan/internal/opgen/register.go index ea7390354d74..0bd2e35a2bab 100644 --- a/pkg/sql/schemachanger/scplan/internal/opgen/register.go +++ b/pkg/sql/schemachanger/scplan/internal/opgen/register.go @@ -230,10 +230,10 @@ func validateTargets(targets []target) error { } for s := range allStatuses { - if !absentStatuses[s] { + if nonAbsentStatuses[s] && !absentStatuses[s] { return errors.Errorf("status %s is featured in non-ABSENT targets but not in the ABSENT target", s) } - if !nonAbsentStatuses[s] { + if absentStatuses[s] && !nonAbsentStatuses[s] { return errors.Errorf("status %s is featured in ABSENT target but not in any non-ABSENT targets", s) } } diff --git a/pkg/sql/schemachanger/scplan/internal/rules/dep_drop_object.go b/pkg/sql/schemachanger/scplan/internal/rules/dep_drop_object.go index d2790fdb2fa2..63708b4a2bc3 100644 --- a/pkg/sql/schemachanger/scplan/internal/rules/dep_drop_object.go +++ b/pkg/sql/schemachanger/scplan/internal/rules/dep_drop_object.go @@ -67,6 +67,7 @@ func init() { to.typeFilter(isSimpleDependent), joinOnDescID(from, to, "desc-id"), statusesToAbsent(from, scpb.Status_DROPPED, to, scpb.Status_ABSENT), + fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL(from.target, from.el, to.el), } }) diff --git a/pkg/sql/schemachanger/scplan/internal/rules/helpers.go b/pkg/sql/schemachanger/scplan/internal/rules/helpers.go index 8e85a135f271..ee89b268b9eb 100644 --- a/pkg/sql/schemachanger/scplan/internal/rules/helpers.go +++ b/pkg/sql/schemachanger/scplan/internal/rules/helpers.go @@ -482,3 +482,30 @@ var descriptorIsNotBeingDropped = screl.Schema.DefNotJoin1( } }, ) + +// fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL creates +// a clause which leads to the outer clause failing to unify +// if the passed element `from` is a Table, `to` is a RowLevelTTl, +// and there does not exist a node with the same target as +// `fromTarget` in PUBLIC status. +// It is used to suppress rule "descriptor drop right before dependent element removal" +// for the special case where we drop a rowLevelTTL table in mixed +// version state for forward compatibility (issue #86672). +var fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL = screl.Schema.DefNotJoin3( + "fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL", + "fromTarget", "fromEl", "toEl", func(fromTarget, fromEl, toEl rel.Var) rel.Clauses { + n := rel.Var("n") + return rel.Clauses{ + fromEl.Type((*scpb.Table)(nil)), + toEl.Type((*scpb.RowLevelTTL)(nil)), + n.Type((*screl.Node)(nil)), + n.AttrEqVar(screl.Target, fromTarget), + screl.Schema.DefNotJoin1("nodeHasNoPublicStatus", "n", func(n rel.Var) rel.Clauses { + public := rel.Var("public") + return rel.Clauses{ + public.Eq(scpb.Status_PUBLIC), + n.AttrEqVar(screl.CurrentStatus, public), + } + })(n), + } + }) diff --git a/pkg/sql/schemachanger/scplan/internal/rules/testdata/deprules b/pkg/sql/schemachanger/scplan/internal/rules/testdata/deprules index 43cb2c98308a..f7c6b756ff99 100644 --- a/pkg/sql/schemachanger/scplan/internal/rules/testdata/deprules +++ b/pkg/sql/schemachanger/scplan/internal/rules/testdata/deprules @@ -15,6 +15,13 @@ descriptorIsNotBeingDropped($element): - joinTarget($descriptor, $descriptor-target) - joinOnDescID($descriptor, $element, $id) - $descriptor-target[TargetStatus] = ABSENT +fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL($fromTarget, $fromEl, $toEl): + not-join: + - $fromEl[Type] = '*scpb.Table' + - $toEl[Type] = '*scpb.RowLevelTTL' + - $n[Type] = '*screl.Node' + - $n[Target] = $fromTarget + - nodeHasNoPublicStatus($n) joinOnColumnID($a, $b, $desc-id, $col-id): - joinOnDescID($a, $b, $desc-id) - $a[ColumnID] = $col-id @@ -41,6 +48,10 @@ joinTargetNode($element, $target, $node): - joinTarget($element, $target) - $node[Type] = '*screl.Node' - $node[Target] = $target +nodeHasNoPublicStatus($n): + not-join: + - $public = PUBLIC + - $n[CurrentStatus] = $public nodeNotExistsWithStatusIn_BACKFILLED_BACKFILL_ONLY($sharedTarget): not-join: - $n[Type] = '*screl.Node' @@ -1664,6 +1675,7 @@ deprules - toAbsent($descriptor-target, $dependent-target) - $descriptor-node[CurrentStatus] = DROPPED - $dependent-node[CurrentStatus] = ABSENT + - fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL($descriptor-target, $descriptor, $dependent) - joinTargetNode($descriptor, $descriptor-target, $descriptor-node) - joinTargetNode($dependent, $dependent-target, $dependent-node) - name: descriptor drop right before removing dependent with attr ref diff --git a/pkg/sql/schemachanger/scplan/internal/rules/testdata/oprules b/pkg/sql/schemachanger/scplan/internal/rules/testdata/oprules index 9131c112a2e2..340e78d09504 100644 --- a/pkg/sql/schemachanger/scplan/internal/rules/testdata/oprules +++ b/pkg/sql/schemachanger/scplan/internal/rules/testdata/oprules @@ -15,6 +15,13 @@ descriptorIsNotBeingDropped($element): - joinTarget($descriptor, $descriptor-target) - joinOnDescID($descriptor, $element, $id) - $descriptor-target[TargetStatus] = ABSENT +fromHasPublicStatusIfFromIsTableAndToIsRowLevelTTL($fromTarget, $fromEl, $toEl): + not-join: + - $fromEl[Type] = '*scpb.Table' + - $toEl[Type] = '*scpb.RowLevelTTL' + - $n[Type] = '*screl.Node' + - $n[Target] = $fromTarget + - nodeHasNoPublicStatus($n) joinOnColumnID($a, $b, $desc-id, $col-id): - joinOnDescID($a, $b, $desc-id) - $a[ColumnID] = $col-id @@ -41,6 +48,10 @@ joinTargetNode($element, $target, $node): - joinTarget($element, $target) - $node[Type] = '*screl.Node' - $node[Target] = $target +nodeHasNoPublicStatus($n): + not-join: + - $public = PUBLIC + - $n[CurrentStatus] = $public nodeNotExistsWithStatusIn_BACKFILLED_BACKFILL_ONLY($sharedTarget): not-join: - $n[Type] = '*screl.Node' diff --git a/pkg/sql/schemachanger/scplan/internal/scgraph/graph.go b/pkg/sql/schemachanger/scplan/internal/scgraph/graph.go index cdae4cf7cbf2..bae828b9cf78 100644 --- a/pkg/sql/schemachanger/scplan/internal/scgraph/graph.go +++ b/pkg/sql/schemachanger/scplan/internal/scgraph/graph.go @@ -23,7 +23,7 @@ import ( "github.com/cockroachdb/redact" ) -// Graph is a graph whose nodes are *scpb.Nodes. Graphs are constructed during +// Graph is a graph whose nodes are *screl.Nodes. Graphs are constructed during // schema change planning. Edges in the graph represent dependencies between // nodes, either due to the sequencing of statuses for a single target or due to // inter-target dependencies between statuses.