|
| 1 | +// Copyright 2022 The Cockroach Authors. |
| 2 | +// |
| 3 | +// Use of this software is governed by the CockroachDB Software License |
| 4 | +// included in the /LICENSE file. |
| 5 | + |
| 6 | +package release_25_3 |
| 7 | + |
| 8 | +import ( |
| 9 | + "reflect" |
| 10 | + "runtime" |
| 11 | + "strings" |
| 12 | + "testing" |
| 13 | + |
| 14 | + "github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb" |
| 15 | + "github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scpb" |
| 16 | + "github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan/internal/opgen" |
| 17 | + "github.com/cockroachdb/cockroach/pkg/sql/schemachanger/screl" |
| 18 | + "github.com/cockroachdb/cockroach/pkg/sql/types" |
| 19 | + "github.com/cockroachdb/errors" |
| 20 | +) |
| 21 | + |
| 22 | +// TestRuleAssertions verifies that important helper functions verify certain |
| 23 | +// properties that the rule definitions rely on. |
| 24 | +func TestRuleAssertions(t *testing.T) { |
| 25 | + for _, fn := range []func(e scpb.Element) error{ |
| 26 | + checkSimpleDependentsReferenceDescID, |
| 27 | + checkToAbsentCategories, |
| 28 | + checkIsWithTypeT, |
| 29 | + checkIsWithExpression, |
| 30 | + checkIsColumnDependent, |
| 31 | + checkIsIndexDependent, |
| 32 | + checkIsConstraintDependent, |
| 33 | + checkConstraintPartitions, |
| 34 | + checkIsTriggerDependent, |
| 35 | + } { |
| 36 | + var fni interface{} = fn |
| 37 | + fullName := runtime.FuncForPC(reflect.ValueOf(fni).Pointer()).Name() |
| 38 | + nameParts := strings.Split(fullName, "rules.") |
| 39 | + shortName := nameParts[len(nameParts)-1] |
| 40 | + t.Run(shortName, func(t *testing.T) { |
| 41 | + _ = scpb.ForEachElementType(func(e scpb.Element) error { |
| 42 | + e = nonNilElement(e) |
| 43 | + if err := fn(e); err != nil { |
| 44 | + t.Errorf("%T: %+v", e, err) |
| 45 | + } |
| 46 | + return nil |
| 47 | + }) |
| 48 | + }) |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +func nonNilElement(element scpb.Element) scpb.Element { |
| 53 | + return reflect.New(reflect.ValueOf(element).Type().Elem()).Interface().(scpb.Element) |
| 54 | +} |
| 55 | + |
| 56 | +// Assert that only simple dependents (non-descriptor, non-index, non-column) |
| 57 | +// and data elements have screl.ReferencedDescID attributes. |
| 58 | +// One exception is foreign key constraint, which is not simple dependent nor data |
| 59 | +// element but it has a screl.ReferencedDescID attribute. |
| 60 | +func checkSimpleDependentsReferenceDescID(e scpb.Element) error { |
| 61 | + if isSimpleDependent(e) || isData(e) { |
| 62 | + return nil |
| 63 | + } |
| 64 | + if _, ok := e.(*scpb.ForeignKeyConstraint); ok { |
| 65 | + return nil |
| 66 | + } |
| 67 | + if _, err := screl.Schema.GetAttribute(screl.ReferencedDescID, e); err == nil { |
| 68 | + return errors.New("unexpected screl.ReferencedDescID attr") |
| 69 | + } |
| 70 | + return nil |
| 71 | +} |
| 72 | + |
| 73 | +// Assert that elements can be grouped into three categories when transitioning |
| 74 | +// from PUBLIC to ABSENT: |
| 75 | +// - go via DROPPED iff they're descriptor or data elements; |
| 76 | +// - go via a non-read status iff they're indexes or columns, which are |
| 77 | +// subject to the two-version invariant; |
| 78 | +// - go direct to ABSENT in all other cases. |
| 79 | +func checkToAbsentCategories(e scpb.Element) error { |
| 80 | + s0 := opgen.InitialStatus(e, scpb.Status_ABSENT) |
| 81 | + s1 := opgen.NextStatus(e, scpb.Status_ABSENT, s0) |
| 82 | + switch s1 { |
| 83 | + case scpb.Status_DROPPED: |
| 84 | + if isDescriptor(e) || isData(e) { |
| 85 | + return nil |
| 86 | + } |
| 87 | + case scpb.Status_VALIDATED, scpb.Status_WRITE_ONLY, scpb.Status_DELETE_ONLY: |
| 88 | + if isSubjectTo2VersionInvariant(e) { |
| 89 | + return nil |
| 90 | + } |
| 91 | + case scpb.Status_ABSENT: |
| 92 | + if isSimpleDependent(e) { |
| 93 | + return nil |
| 94 | + } |
| 95 | + // TableSchemaLocked is subject to the two version invariant rule, so that |
| 96 | + // toggling the lock will have descriptor version bumps in between. |
| 97 | + // However, it is allowed direct transitions from PUBLIC -> ABSENT / |
| 98 | + // ABSENT -> PUBLIC. |
| 99 | + if isTableSchemaLocked(e) { |
| 100 | + return nil |
| 101 | + } |
| 102 | + } |
| 103 | + return errors.Newf("unexpected transition %s -> %s in direction ABSENT", s0, s1) |
| 104 | +} |
| 105 | + |
| 106 | +// Assert that isWithTypeT covers all elements with embedded TypeTs. |
| 107 | +func checkIsWithTypeT(e scpb.Element) error { |
| 108 | + return screl.WalkTypes(e, func(t *types.T) error { |
| 109 | + if isWithTypeT(e) { |
| 110 | + return nil |
| 111 | + } |
| 112 | + return errors.New("should verify isWithTypeT but doesn't") |
| 113 | + }) |
| 114 | +} |
| 115 | + |
| 116 | +// Assert that isWithExpression covers all elements with embedded |
| 117 | +// expressions. |
| 118 | +func checkIsWithExpression(e scpb.Element) error { |
| 119 | + return screl.WalkExpressions(e, func(t *catpb.Expression) error { |
| 120 | + switch e.(type) { |
| 121 | + // Ignore elements which have catpb.Expression fields but which don't |
| 122 | + // have them within an scpb.Expression for valid reasons. |
| 123 | + case *scpb.RowLevelTTL: |
| 124 | + return nil |
| 125 | + } |
| 126 | + if isWithExpression(e) { |
| 127 | + return nil |
| 128 | + } |
| 129 | + return errors.Newf("should verify isWithExpression but doesn't: %T", e) |
| 130 | + }) |
| 131 | +} |
| 132 | + |
| 133 | +// Assert that isColumnDependent covers all dependent elements of a column |
| 134 | +// element. |
| 135 | +func checkIsColumnDependent(e scpb.Element) error { |
| 136 | + // Exclude columns themselves. |
| 137 | + if isColumn(e) { |
| 138 | + return nil |
| 139 | + } |
| 140 | + // A column dependent should have a ColumnID attribute. |
| 141 | + _, err := screl.Schema.GetAttribute(screl.ColumnID, e) |
| 142 | + if isColumnDependent(e) { |
| 143 | + if err != nil { |
| 144 | + return errors.New("verifies isColumnDependent but doesn't have ColumnID attr") |
| 145 | + } |
| 146 | + } else if err == nil { |
| 147 | + return errors.New("has ColumnID attr but doesn't verify isColumnDependent") |
| 148 | + } |
| 149 | + return nil |
| 150 | +} |
| 151 | + |
| 152 | +// Assert that isIndexDependent covers all dependent elements of an index |
| 153 | +// element. |
| 154 | +func checkIsIndexDependent(e scpb.Element) error { |
| 155 | + // Exclude indexes themselves and their data. |
| 156 | + if isIndex(e) || isData(e) || isNonIndexBackedConstraint(e) { |
| 157 | + return nil |
| 158 | + } |
| 159 | + // An index dependent should have an IndexID attribute. |
| 160 | + _, err := screl.Schema.GetAttribute(screl.IndexID, e) |
| 161 | + if isIndexDependent(e) { |
| 162 | + if err != nil { |
| 163 | + return errors.New("verifies isIndexDependent but doesn't have IndexID attr") |
| 164 | + } |
| 165 | + } else if err == nil { |
| 166 | + return errors.New("has IndexID attr but doesn't verify isIndexDependent") |
| 167 | + } |
| 168 | + return nil |
| 169 | +} |
| 170 | + |
| 171 | +// Assert that checkIsConstraintDependent covers all elements of a constraint |
| 172 | +// element. |
| 173 | +func checkIsConstraintDependent(e scpb.Element) error { |
| 174 | + // Exclude constraints themselves. |
| 175 | + if isConstraint(e) { |
| 176 | + return nil |
| 177 | + } |
| 178 | + // A constraint dependent should have a ConstraintID attribute. |
| 179 | + _, err := screl.Schema.GetAttribute(screl.ConstraintID, e) |
| 180 | + if isConstraintDependent(e) { |
| 181 | + if err != nil { |
| 182 | + return errors.New("verifies isConstraintDependent but doesn't have ConstraintID attr") |
| 183 | + } |
| 184 | + } else if err == nil { |
| 185 | + return errors.New("has ConstraintID attr but doesn't verify isConstraintDependent") |
| 186 | + } |
| 187 | + return nil |
| 188 | +} |
| 189 | + |
| 190 | +// Assert the following partitions about constraints: |
| 191 | +// 1. An element `e` with ConstraintID attr is either a constraint |
| 192 | +// or a constraint dependent. |
| 193 | +// 2. A constraint is either index-backed or non-index-backed. |
| 194 | +// |
| 195 | +// TODO (xiang): Add test for cross-descriptor partition. We currently |
| 196 | +// cannot have them until we added referenced.*ID attr for |
| 197 | +// UniqueWithoutIndex[NotValid] element, which is required to support |
| 198 | +// partial unique without index constraint with a predicate that references |
| 199 | +// other descriptors. |
| 200 | +func checkConstraintPartitions(e scpb.Element) error { |
| 201 | + _, err := screl.Schema.GetAttribute(screl.ConstraintID, e) |
| 202 | + if err != nil { |
| 203 | + return nil //nolint:returnerrcheck |
| 204 | + } |
| 205 | + if !isConstraint(e) && !isConstraintDependent(e) { |
| 206 | + return errors.New("has ConstraintID attr but is not a constraint nor a constraint dependent") |
| 207 | + } |
| 208 | + if isConstraintDependent(e) { |
| 209 | + return nil |
| 210 | + } |
| 211 | + if !isNonIndexBackedConstraint(e) && !isIndex(e) { |
| 212 | + return errors.New("verifies isConstraint but does not verify isNonIndexBackedConstraint nor isIndex") |
| 213 | + } |
| 214 | + return nil |
| 215 | +} |
| 216 | + |
| 217 | +// Assert that checkIsTriggerDependent covers all elements of a trigger element. |
| 218 | +func checkIsTriggerDependent(e scpb.Element) error { |
| 219 | + // Exclude triggers themselves. |
| 220 | + switch e.(type) { |
| 221 | + case *scpb.Trigger: |
| 222 | + return nil |
| 223 | + } |
| 224 | + // A trigger dependent should have a TriggerID attribute. |
| 225 | + _, err := screl.Schema.GetAttribute(screl.TriggerID, e) |
| 226 | + if isTriggerDependent(e) { |
| 227 | + if err != nil { |
| 228 | + return errors.New("verifies isTriggerDependent but doesn't have TriggerID attr") |
| 229 | + } |
| 230 | + } else if err == nil { |
| 231 | + return errors.New("has TriggerID attr but doesn't verify isTriggerDependent") |
| 232 | + } |
| 233 | + return nil |
| 234 | +} |
0 commit comments