From 98ec2048f0c64b28c4020d645ba96f239ee4c429 Mon Sep 17 00:00:00 2001 From: Marius Posta Date: Wed, 16 Nov 2022 12:22:46 -0500 Subject: [PATCH 1/2] catalog,tabledesc: add & adopt Constraint subtypes These new interfaces help encapsulate the descriptor protobufs for table constraints: - CheckConstraint, - ForeignKeyConstraint, - UniqueWithoutIndexConstraint, - UniqueWithIndexConstraint. These are the leaves of the Constraint type tree, and Constraint is the interface at the root of it. The changes in this commit mimic what was done for Column & Index, which respectively wrap descpb.ColumnDescriptor and descpb.IndexDescriptor. These new constraint interfaces are adopted liberally throughout the code base, but not completely: many references to descpb-protobufs still need to be replaced. This commit also removes the somewhat confusing concept of "active" versus "inactive" constraint. Not only was it confusing to me, evidently it was also confusing to other contributors considering how several mild bugs and inconsistencies made their way into the code. This commit replaces this concept with that of an "enforced" constraint: - A constraint is enforced if it applies to data written to the table, regardless of whether it has been validated on the data already in the table prior to the constraint coming into existence. The implementation is somewhat awkward for constraints in that the same constraint descriptor can be featured twice inside a descpb.TableDescriptor, in its active constraint slice (such as Checks, etc.) and also in the Mutations slice. In these cases the interface wraps the non-mutation constraint descriptor protobuf, with no observed changes to the database's behavior. This commit required alterations to the table descriptor's post-deserialization changes. The change which assigns constraint IDs to table descriptors which don't have themhad some subtle bugs which went unnoticed until this commit forced a heavier reliance on these fields. This commit also adds a new change which ensures that a check constraint's ColumnIDs slice is populated based on the columns referenced in its expression, replacing ancient code which predates the concept of post-deserialization changes and which would compute this lazily. As a more general observation: it appears that the handling of adding and dropping constraints following other schema changes was mildly inconsistent and this commit tries to improve this. For instance, it was possible to drop a column which had been set as NOT NULL in the same transaction, but not do the same for any other kind of constraint. Also it was possible to reuse the name of a dropping constraint, despite this constraint still being enforced. The new behavior is more strict, yet this should be barely noticeable by the user in most practical cases: it's rare that one wants to add a constraint referencing a column and also drop that column in the same transaction, for instance. Fixes #91918. Release note: None --- pkg/sql/alter_column_type.go | 47 +- pkg/sql/alter_primary_key.go | 15 +- pkg/sql/alter_table.go | 363 ++++------- pkg/sql/backfill.go | 84 +-- pkg/sql/backfill_test.go | 44 +- pkg/sql/catalog/descpb/index.go | 22 +- pkg/sql/catalog/descpb/structured.go | 28 - pkg/sql/catalog/descriptor.go | 124 ++-- .../catalog/post_deserialization_changes.go | 4 + pkg/sql/catalog/table_elements.go | 198 +++++- pkg/sql/catalog/tabledesc/BUILD.bazel | 1 - pkg/sql/catalog/tabledesc/constraint.go | 614 ++++++++++++------ pkg/sql/catalog/tabledesc/constraint_test.go | 229 ++++--- pkg/sql/catalog/tabledesc/helpers_test.go | 2 +- pkg/sql/catalog/tabledesc/index.go | 100 +-- pkg/sql/catalog/tabledesc/mutation.go | 200 +++--- pkg/sql/catalog/tabledesc/safe_format_test.go | 1 + pkg/sql/catalog/tabledesc/structured.go | 407 ++---------- pkg/sql/catalog/tabledesc/structured_test.go | 21 +- pkg/sql/catalog/tabledesc/table.go | 239 ++----- pkg/sql/catalog/tabledesc/table_desc.go | 71 +- .../catalog/tabledesc/table_desc_builder.go | 220 ++++--- pkg/sql/catalog/tabledesc/validate.go | 168 ++--- pkg/sql/catalog/tabledesc/validate_test.go | 338 ++++++---- pkg/sql/check.go | 59 +- pkg/sql/comment_on_constraint.go | 13 +- pkg/sql/crdb_internal.go | 89 ++- pkg/sql/create_index.go | 9 +- pkg/sql/create_table.go | 34 +- pkg/sql/drop_index.go | 73 +-- pkg/sql/drop_table.go | 30 +- pkg/sql/importer/import_job.go | 6 +- pkg/sql/information_schema.go | 183 +++--- .../logictest/testdata/logic_test/alter_table | 33 +- .../logictest/testdata/logic_test/pg_catalog | 114 ++-- .../testdata/logic_test/schema_change_in_txn | 2 +- pkg/sql/opt_catalog.go | 73 +-- pkg/sql/pg_catalog.go | 279 ++++---- pkg/sql/pgwire/testdata/pgtest/notice | 2 +- pkg/sql/rename_table.go | 27 +- pkg/sql/schema_changer.go | 33 +- pkg/sql/schema_changer_test.go | 20 +- pkg/sql/schemachanger/scdecomp/decomp.go | 68 +- .../scdeps/sctestdeps/test_deps.go | 2 +- pkg/sql/schemachanger/scdeps/validator.go | 4 +- pkg/sql/schemachanger/scexec/dependencies.go | 2 +- .../schemachanger/scexec/exec_validation.go | 5 +- .../scexec/executor_external_test.go | 2 +- .../scexec/scmutationexec/references.go | 10 +- pkg/sql/scrub.go | 59 +- pkg/sql/scrub_constraint.go | 11 +- pkg/sql/scrub_fk.go | 43 +- pkg/sql/scrub_unique_constraint.go | 47 +- pkg/sql/show_create.go | 13 +- pkg/sql/show_create_clauses.go | 40 +- pkg/sql/show_create_table_test.go | 6 +- pkg/sql/table_test.go | 2 +- pkg/sql/tests/hash_sharded_test.go | 6 +- pkg/sql/truncate.go | 4 +- pkg/sql/type_change.go | 4 +- .../fix_userfile_descriptor_corruption.go | 24 +- .../upgrades/tenant_table_migration_test.go | 2 +- 62 files changed, 2409 insertions(+), 2564 deletions(-) diff --git a/pkg/sql/alter_column_type.go b/pkg/sql/alter_column_type.go index 81ae1caf0a88..243485a711fc 100644 --- a/pkg/sql/alter_column_type.go +++ b/pkg/sql/alter_column_type.go @@ -215,52 +215,31 @@ func alterColumnTypeGeneral( // Disallow ALTER COLUMN TYPE general for columns that have a // UNIQUE WITHOUT INDEX constraint. - for _, uc := range tableDesc.AllActiveAndInactiveUniqueWithoutIndexConstraints() { - for _, id := range uc.ColumnIDs { - if col.GetID() == id { - return colWithConstraintNotSupportedErr - } + for _, uc := range tableDesc.UniqueConstraintsWithoutIndex() { + if uc.CollectKeyColumnIDs().Contains(col.GetID()) { + return colWithConstraintNotSupportedErr } } // Disallow ALTER COLUMN TYPE general for columns that have a foreign key // constraint. - for _, fk := range tableDesc.AllActiveAndInactiveForeignKeys() { - if fk.OriginTableID == tableDesc.GetID() { - for _, id := range fk.OriginColumnIDs { - if col.GetID() == id { - return colWithConstraintNotSupportedErr - } - } + for _, fk := range tableDesc.OutboundForeignKeys() { + if fk.CollectOriginColumnIDs().Contains(col.GetID()) { + return colWithConstraintNotSupportedErr } - if fk.ReferencedTableID == tableDesc.GetID() { - for _, id := range fk.ReferencedColumnIDs { - if col.GetID() == id { - return colWithConstraintNotSupportedErr - } - } + if fk.GetReferencedTableID() == tableDesc.GetID() && + fk.CollectReferencedColumnIDs().Contains(col.GetID()) { + return colWithConstraintNotSupportedErr } } // Disallow ALTER COLUMN TYPE general for columns that are // part of indexes. for _, idx := range tableDesc.NonDropIndexes() { - for i := 0; i < idx.NumKeyColumns(); i++ { - if idx.GetKeyColumnID(i) == col.GetID() { - return colInIndexNotSupportedErr - } - } - for i := 0; i < idx.NumKeySuffixColumns(); i++ { - if idx.GetKeySuffixColumnID(i) == col.GetID() { - return colInIndexNotSupportedErr - } - } - if !idx.Primary() { - for i := 0; i < idx.NumSecondaryStoredColumns(); i++ { - if idx.GetStoredColumnID(i) == col.GetID() { - return colInIndexNotSupportedErr - } - } + if idx.CollectKeyColumnIDs().Contains(col.GetID()) || + idx.CollectKeySuffixColumnIDs().Contains(col.GetID()) || + idx.CollectSecondaryStoredColumnIDs().Contains(col.GetID()) { + return colInIndexNotSupportedErr } } diff --git a/pkg/sql/alter_primary_key.go b/pkg/sql/alter_primary_key.go index de448c3aa344..7de746c8b20f 100644 --- a/pkg/sql/alter_primary_key.go +++ b/pkg/sql/alter_primary_key.go @@ -513,22 +513,14 @@ func (p *planner) AlterPrimaryKey( // Determine if removing this index would lead to the uniqueness for a foreign // key back reference, which will cause this swap operation to be blocked. - nonDropIndexes := tableDesc.NonDropIndexes() - remainingIndexes := make([]descpb.UniqueConstraint, 0, len(nonDropIndexes)) - for i := range nonDropIndexes { - // We can't copy directly because of the interface conversion. - if nonDropIndexes[i].GetID() == tableDesc.GetPrimaryIndex().GetID() { - continue - } - remainingIndexes = append(remainingIndexes, nonDropIndexes[i]) - } - remainingIndexes = append(remainingIndexes, newPrimaryIndexDesc) + const withSearchForReplacement = true err = p.tryRemoveFKBackReferences( ctx, tableDesc, tableDesc.GetPrimaryIndex(), tree.DropRestrict, - remainingIndexes) + withSearchForReplacement, + ) if err != nil { return err } @@ -737,6 +729,7 @@ func addIndexMutationWithSpecificPrimaryKey( ) error { // Reset the ID so that a call to AllocateIDs will set up the index. toAdd.ID = 0 + toAdd.ConstraintID = 0 if err := table.AddIndexMutationMaybeWithTempIndex(toAdd, descpb.DescriptorMutation_ADD); err != nil { return err } diff --git a/pkg/sql/alter_table.go b/pkg/sql/alter_table.go index 033a9fc6cdb9..d0c8e2d1bd36 100644 --- a/pkg/sql/alter_table.go +++ b/pkg/sql/alter_table.go @@ -353,14 +353,9 @@ func (n *alterTableNode) startExec(params runParams) error { case *tree.CheckConstraintTableDef: var err error params.p.runWithOptions(resolveFlags{contextDatabaseID: n.tableDesc.ParentID}, func() { - info, infoErr := n.tableDesc.GetConstraintInfo() - if infoErr != nil { - err = infoErr - return - } ckBuilder := schemaexpr.MakeCheckConstraintBuilder(params.ctx, *tn, n.tableDesc, ¶ms.p.semaCtx) - for k := range info { - ckBuilder.MarkNameInUse(k) + for _, c := range n.tableDesc.AllConstraints() { + ckBuilder.MarkNameInUse(c.GetName()) } ck, buildErr := ckBuilder.Build(d) if buildErr != nil { @@ -508,25 +503,18 @@ func (n *alterTableNode) startExec(params runParams) error { } droppedViews = append(droppedViews, colDroppedViews...) case *tree.AlterTableDropConstraint: - info, err := n.tableDesc.GetConstraintInfo() - if err != nil { - return err - } name := string(t.Constraint) - details, ok := info[name] - if !ok { + c, _ := n.tableDesc.FindConstraintWithName(name) + if c == nil { if t.IfExists { continue } return pgerror.Newf(pgcode.UndefinedObject, "constraint %q of relation %q does not exist", t.Constraint, n.tableDesc.Name) } - if err := n.tableDesc.DropConstraint( - params.ctx, - name, details, - func(desc *tabledesc.Mutable, ref *descpb.ForeignKeyConstraint) error { - return params.p.removeFKBackReference(params.ctx, desc, ref) - }, params.ExecCfg().Settings); err != nil { + if err := n.tableDesc.DropConstraint(c, func(backRef catalog.ForeignKeyConstraint) error { + return params.p.removeFKBackReference(params.ctx, n.tableDesc, backRef.ForeignKeyDesc()) + }); err != nil { return err } descriptorChanged = true @@ -535,101 +523,52 @@ func (n *alterTableNode) startExec(params runParams) error { } case *tree.AlterTableValidateConstraint: - info, err := n.tableDesc.GetConstraintInfo() - if err != nil { - return err - } name := string(t.Constraint) - constraint, ok := info[name] - if !ok { + c, _ := n.tableDesc.FindConstraintWithName(name) + if c == nil { return pgerror.Newf(pgcode.UndefinedObject, "constraint %q of relation %q does not exist", t.Constraint, n.tableDesc.Name) } - if !constraint.Unvalidated { + switch c.GetConstraintValidity() { + case descpb.ConstraintValidity_Validated: + // Nothing to do. continue + case descpb.ConstraintValidity_Validating: + return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "constraint %q in the middle of being added, try again later", t.Constraint) + case descpb.ConstraintValidity_Dropping: + return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "constraint %q in the middle of being dropped", t.Constraint) } - switch constraint.Kind { - case descpb.ConstraintTypeCheck: - found := false - var ck *descpb.TableDescriptor_CheckConstraint - for _, c := range n.tableDesc.Checks { - // If the constraint is still being validated, don't allow - // VALIDATE CONSTRAINT to run. - if c.Name == name && c.Validity != descpb.ConstraintValidity_Validating { - found = true - ck = c - break - } - } - if !found { - return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, - "constraint %q in the middle of being added, try again later", t.Constraint) - } + if ck := c.AsCheck(); ck != nil { if err := params.p.WithInternalExecutor(params.ctx, func(ctx context.Context, txn *kv.Txn, ie sqlutil.InternalExecutor) error { - return validateCheckInTxn(ctx, ¶ms.p.semaCtx, params.p.SessionData(), n.tableDesc, txn, ie, ck.Expr) + return validateCheckInTxn(ctx, ¶ms.p.semaCtx, params.p.SessionData(), n.tableDesc, txn, ie, ck.GetExpr()) }); err != nil { return err } - ck.Validity = descpb.ConstraintValidity_Validated - - case descpb.ConstraintTypeFK: - var foundFk *descpb.ForeignKeyConstraint - for i := range n.tableDesc.OutboundFKs { - fk := &n.tableDesc.OutboundFKs[i] - // If the constraint is still being validated, don't allow - // VALIDATE CONSTRAINT to run. - if fk.Name == name && fk.Validity != descpb.ConstraintValidity_Validating { - foundFk = fk - break - } - } - if foundFk == nil { - return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, - "constraint %q in the middle of being added, try again later", t.Constraint) - } + ck.CheckDesc().Validity = descpb.ConstraintValidity_Validated + } else if fk := c.AsForeignKey(); fk != nil { if err := params.p.WithInternalExecutor(params.ctx, func(ctx context.Context, txn *kv.Txn, ie sqlutil.InternalExecutor) error { return validateFkInTxn(ctx, n.tableDesc, txn, ie, params.p.descCollection, name) }); err != nil { return err } - foundFk.Validity = descpb.ConstraintValidity_Validated - case descpb.ConstraintTypeUnique: - if constraint.Index == nil { - var foundUnique *descpb.UniqueWithoutIndexConstraint - for i := range n.tableDesc.UniqueWithoutIndexConstraints { - uc := &n.tableDesc.UniqueWithoutIndexConstraints[i] - // If the constraint is still being validated, don't allow - // VALIDATE CONSTRAINT to run. - if uc.Name == name && uc.Validity != descpb.ConstraintValidity_Validating { - foundUnique = uc - break - } - } - if foundUnique == nil { - return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, - "constraint %q in the middle of being added, try again later", t.Constraint) - } - if err := params.p.WithInternalExecutor(params.ctx, func(ctx context.Context, txn *kv.Txn, ie sqlutil.InternalExecutor) error { - return validateUniqueWithoutIndexConstraintInTxn( - params.ctx, - n.tableDesc, - txn, - ie, - params.p.User(), - name, - ) - }); err != nil { - return err - } - foundUnique.Validity = descpb.ConstraintValidity_Validated - break + fk.ForeignKeyDesc().Validity = descpb.ConstraintValidity_Validated + } else if uwoi := c.AsUniqueWithoutIndex(); uwoi != nil { + if err := params.p.WithInternalExecutor(params.ctx, func(ctx context.Context, txn *kv.Txn, ie sqlutil.InternalExecutor) error { + return validateUniqueWithoutIndexConstraintInTxn( + params.ctx, + n.tableDesc, + txn, + ie, + params.p.User(), + name, + ) + }); err != nil { + return err } - - // This unique constraint is enforced by an index, so fall through to - // the error below. - fallthrough - - default: + uwoi.UniqueWithoutIndexDesc().Validity = descpb.ConstraintValidity_Validated + } else { return pgerror.Newf(pgcode.WrongObjectType, "constraint %q of relation %q is not a foreign key, check, or unique without index"+ " constraint", tree.ErrString(&t.Constraint), tree.ErrString(n.n.Table)) @@ -810,12 +749,8 @@ func (n *alterTableNode) startExec(params runParams) error { descriptorChanged = descriptorChanged || descChanged case *tree.AlterTableRenameConstraint: - info, err := n.tableDesc.GetConstraintInfo() - if err != nil { - return err - } - details, ok := info[string(t.Constraint)] - if !ok { + constraint, _ := n.tableDesc.FindConstraintWithName(string(t.Constraint)) + if constraint == nil { return pgerror.Newf(pgcode.UndefinedObject, "constraint %q of relation %q does not exist", tree.ErrString(&t.Constraint), n.tableDesc.Name) } @@ -823,16 +758,22 @@ func (n *alterTableNode) startExec(params runParams) error { // Nothing to do. break } - - if _, ok := info[string(t.NewName)]; ok { + switch constraint.GetConstraintValidity() { + case descpb.ConstraintValidity_Validating: + return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "constraint %q in the middle of being added, try again later", t.Constraint) + case descpb.ConstraintValidity_Dropping: + return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "constraint %q in the middle of being dropped", t.Constraint) + } + if other, _ := n.tableDesc.FindConstraintWithName(string(t.NewName)); other != nil { return pgerror.Newf(pgcode.DuplicateObject, "duplicate constraint name: %q", tree.ErrString(&t.NewName)) } // If this is a unique or primary constraint, renames of the constraint // lead to renames of the underlying index. Ensure that no index with this // new name exists. This is what postgres does. - switch details.Kind { - case descpb.ConstraintTypeUnique, descpb.ConstraintTypePK: + if constraint.AsUniqueWithIndex() != nil { if catalog.FindNonDropIndex(n.tableDesc, func(idx catalog.Index) bool { return idx.GetName() == string(t.NewName) }) != nil { @@ -851,10 +792,9 @@ func (n *alterTableNode) startExec(params runParams) error { ) } - if err := n.tableDesc.RenameConstraint( - details, string(t.Constraint), string(t.NewName), depViewRenameError, - func(desc *tabledesc.Mutable, ref *descpb.ForeignKeyConstraint, newName string) error { - return params.p.updateFKBackReferenceName(params.ctx, desc, ref, newName) + if err := n.tableDesc.RenameConstraint(constraint, string(t.NewName), depViewRenameError, + func(desc *tabledesc.Mutable, ref catalog.ForeignKeyConstraint, newName string) error { + return params.p.updateFKBackReferenceName(params.ctx, desc, ref.ForeignKeyDesc(), newName) }); err != nil { return err } @@ -1064,19 +1004,11 @@ func applyColumnMutation( "constraint in the middle of being dropped") } } - info, err := tableDesc.GetConstraintInfo() - if err != nil { - return err - } - inuseNames := make(map[string]struct{}, len(info)) - for k := range info { - inuseNames[k] = struct{}{} - } col.ColumnDesc().Nullable = true // Add a check constraint equivalent to the non-null constraint and drop // it in the schema changer. - check := tabledesc.MakeNotNullCheckConstraint(col.GetName(), col.GetID(), tableDesc.GetNextConstraintID(), inuseNames, descpb.ConstraintValidity_Dropping) + check := tabledesc.MakeNotNullCheckConstraint(tableDesc, col, descpb.ConstraintValidity_Dropping) tableDesc.Checks = append(tableDesc.Checks, check) tableDesc.NextConstraintID++ tableDesc.AddNotNullMutation(check, descpb.DescriptorMutation_DROP) @@ -1096,15 +1028,7 @@ func applyColumnMutation( } func addNotNullConstraintMutationForCol(tableDesc *tabledesc.Mutable, col catalog.Column) error { - info, err := tableDesc.GetConstraintInfo() - if err != nil { - return err - } - inuseNames := make(map[string]struct{}, len(info)) - for k := range info { - inuseNames[k] = struct{}{} - } - check := tabledesc.MakeNotNullCheckConstraint(col.GetName(), col.GetID(), tableDesc.GetNextConstraintID(), inuseNames, descpb.ConstraintValidity_Validating) + check := tabledesc.MakeNotNullCheckConstraint(tableDesc, col, descpb.ConstraintValidity_Validating) tableDesc.AddNotNullMutation(check, descpb.DescriptorMutation_ADD) tableDesc.NextConstraintID++ return nil @@ -1496,7 +1420,7 @@ func validateConstraintNameIsNotUsed( // ok with the conflict in this case. defaultPKName := tabledesc.PrimaryKeyIndexName(tableDesc.GetName()) if tableDesc.HasPrimaryKey() && tableDesc.IsPrimaryIndexDefaultRowID() && - tableDesc.PrimaryIndex.GetName() == defaultPKName && + tableDesc.PrimaryIndex.Name == defaultPKName && name == tree.Name(defaultPKName) { return false, nil } @@ -1532,28 +1456,20 @@ func validateConstraintNameIsNotUsed( if name == "" { return false, nil } - info, err := tableDesc.GetConstraintInfo() - if err != nil { - // Unexpected error: table descriptor should be valid at this point. - return false, errors.WithAssertionFailure(err) - } - constraintInfo, isInUse := info[name.String()] - if !isInUse { + constraint, _ := tableDesc.FindConstraintWithName(string(name)) + if constraint == nil { return false, nil } // If the primary index is being replaced, then the name can be reused for // another constraint. - if isInUse && - constraintInfo.Index != nil && - constraintInfo.Index.ID == tableDesc.PrimaryIndex.ID { + if u := constraint.AsUniqueWithIndex(); u != nil && u.GetID() == tableDesc.GetPrimaryIndexID() { for _, mut := range tableDesc.GetMutations() { if primaryKeySwap := mut.GetPrimaryKeySwap(); primaryKeySwap != nil && - primaryKeySwap.OldPrimaryIndexId == tableDesc.PrimaryIndex.ID && + primaryKeySwap.OldPrimaryIndexId == u.GetID() && primaryKeySwap.NewPrimaryIndexName != name.String() { return false, nil } } - } if hasIfNotExists { return true, nil @@ -1727,49 +1643,9 @@ func dropColumnImpl( for _, idx := range tableDesc.NonDropIndexes() { // We automatically drop indexes that reference the column // being dropped. - - // containsThisColumn becomes true if the index is defined - // over the column being dropped. - containsThisColumn := false - - // Analyze the index. - for j := 0; j < idx.NumKeyColumns(); j++ { - if idx.GetKeyColumnID(j) == colToDrop.GetID() { - containsThisColumn = true - break - } - } - if !containsThisColumn { - for j := 0; j < idx.NumKeySuffixColumns(); j++ { - id := idx.GetKeySuffixColumnID(j) - if tableDesc.GetPrimaryIndex().CollectKeyColumnIDs().Contains(id) { - // All secondary indices necessary contain the PK - // columns, too. (See the comments on the definition of - // IndexDescriptor). The presence of a PK column in the - // secondary index should thus not be seen as a - // sufficient reason to reject the DROP. - continue - } - if id == colToDrop.GetID() { - containsThisColumn = true - break - } - } - } - if !containsThisColumn { - // The loop above this comment is for the old STORING encoding. The - // loop below is for the new encoding (where the STORING columns are - // always in the value part of a KV). - for j := 0; j < idx.NumSecondaryStoredColumns(); j++ { - if idx.GetStoredColumnID(j) == colToDrop.GetID() { - containsThisColumn = true - break - } - } - } - - // If the column being dropped is referenced in the partial - // index predicate, then the index should be dropped. + containsThisColumn := idx.CollectKeyColumnIDs().Contains(colToDrop.GetID()) || + idx.CollectKeySuffixColumnIDs().Contains(colToDrop.GetID()) || + idx.CollectSecondaryStoredColumnIDs().Contains(colToDrop.GetID()) if !containsThisColumn && idx.IsPartial() { expr, err := parser.ParseExpr(idx.GetPredicate()) if err != nil { @@ -1785,7 +1661,6 @@ func dropColumnImpl( containsThisColumn = true } } - // Perform the DROP. if containsThisColumn { idxNamesToDelete = append(idxNamesToDelete, idx.GetName()) @@ -1807,56 +1682,37 @@ func dropColumnImpl( } } - // Drop unique constraints that reference the column. - sliceIdx := 0 - for i := range tableDesc.UniqueWithoutIndexConstraints { - constraint := &tableDesc.UniqueWithoutIndexConstraints[i] - tableDesc.UniqueWithoutIndexConstraints[sliceIdx] = *constraint - sliceIdx++ - if descpb.ColumnIDs(constraint.ColumnIDs).Contains(colToDrop.GetID()) { - sliceIdx-- - - // If this unique constraint is used on the referencing side of any FK - // constraints, try to remove the references. Don't bother trying to find - // an alternate index or constraint, since all possible matches will - // be dropped when the column is dropped. - if err := params.p.tryRemoveFKBackReferences( - params.ctx, tableDesc, constraint, t.DropBehavior, nil, - ); err != nil { - return nil, err - } + // Drop non-index-backed unique constraints which reference the column. + for _, uwoi := range tableDesc.EnforcedUniqueConstraintsWithoutIndex() { + if uwoi.Dropped() || !uwoi.CollectKeyColumnIDs().Contains(colToDrop.GetID()) { + continue + } + // If this unique constraint is used on the referencing side of any FK + // constraints, try to remove the references. Don't bother trying to find + // an alternate index or constraint, since all possible matches will + // be dropped when the column is dropped. + const withSearchForReplacement = false + if err := params.p.tryRemoveFKBackReferences( + params.ctx, tableDesc, uwoi, t.DropBehavior, withSearchForReplacement, + ); err != nil { + return nil, err + } + if err := tableDesc.DropConstraint(uwoi, nil /* removeFKBackRef */); err != nil { + return nil, err } } - tableDesc.UniqueWithoutIndexConstraints = tableDesc.UniqueWithoutIndexConstraints[:sliceIdx] // Drop check constraints which reference the column. - constraintsToDrop := make([]string, 0, len(tableDesc.Checks)) - constraintInfo, err := tableDesc.GetConstraintInfo() - if err != nil { - return nil, err - } - - for _, check := range tableDesc.AllActiveAndInactiveChecks() { - if used, err := tableDesc.CheckConstraintUsesColumn(check, colToDrop.GetID()); err != nil { + for _, check := range tableDesc.CheckConstraints() { + if check.Dropped() { + continue + } + if used, err := tableDesc.CheckConstraintUsesColumn(check.CheckDesc(), colToDrop.GetID()); err != nil { return nil, err - } else if used { - if check.Validity == descpb.ConstraintValidity_Dropping { - // We don't need to drop this constraint, its already - // in the process. - continue - } - constraintsToDrop = append(constraintsToDrop, check.Name) + } else if !used { + continue } - } - - for _, constraintName := range constraintsToDrop { - err := tableDesc.DropConstraint(params.ctx, constraintName, constraintInfo[constraintName], - func(*tabledesc.Mutable, *descpb.ForeignKeyConstraint) error { - return nil - }, - params.extendedEvalCtx.Settings, - ) - if err != nil { + if err := tableDesc.DropConstraint(check, nil /* removeFKBackRef */); err != nil { return nil, err } } @@ -1869,7 +1725,7 @@ func dropColumnImpl( // the drop index codepaths aren't going to remove dependent FKs, so we // need to do that here. // We update the FK's slice in place here. - sliceIdx = 0 + sliceIdx := 0 for i := range tableDesc.OutboundFKs { tableDesc.OutboundFKs[sliceIdx] = tableDesc.OutboundFKs[i] sliceIdx++ @@ -2065,18 +1921,29 @@ func handleTTLStorageParamChange( func (p *planner) tryRemoveFKBackReferences( ctx context.Context, tableDesc *tabledesc.Mutable, - constraint descpb.UniqueConstraint, + uniqueConstraint catalog.UniqueConstraint, behavior tree.DropBehavior, - candidateConstraints []descpb.UniqueConstraint, + withSearchForReplacement bool, ) error { - // uniqueConstraintHasReplacementCandidate runs - // IsValidReferencedUniqueConstraint on the candidateConstraints. Returns true - // if at least one constraint satisfies IsValidReferencedUniqueConstraint. + isSuitable := func(fk catalog.ForeignKeyConstraint, u catalog.UniqueConstraint) bool { + return u.GetConstraintID() != uniqueConstraint.GetConstraintID() && !u.Dropped() && + u.IsValidReferencedUniqueConstraint(fk) + } + uwis := tableDesc.UniqueConstraintsWithIndex() + uwois := tableDesc.UniqueConstraintsWithoutIndex() uniqueConstraintHasReplacementCandidate := func( - referencedColumnIDs []descpb.ColumnID, + fk catalog.ForeignKeyConstraint, ) bool { - for _, uc := range candidateConstraints { - if uc.IsValidReferencedUniqueConstraint(referencedColumnIDs) { + if !withSearchForReplacement { + return false + } + for _, uwi := range uwis { + if isSuitable(fk, uwi) { + return true + } + } + for _, uwoi := range uwois { + if isSuitable(fk, uwoi) { return true } } @@ -2085,17 +1952,17 @@ func (p *planner) tryRemoveFKBackReferences( // Index for updating the FK slices in place when removing FKs. sliceIdx := 0 - for i := range tableDesc.InboundFKs { + for i, fk := range tableDesc.InboundForeignKeys() { tableDesc.InboundFKs[sliceIdx] = tableDesc.InboundFKs[i] sliceIdx++ - fk := &tableDesc.InboundFKs[i] - // The constraint being deleted could potentially be the referenced unique - // constraint for this fk. - if constraint.IsValidReferencedUniqueConstraint(fk.ReferencedColumnIDs) && - !uniqueConstraintHasReplacementCandidate(fk.ReferencedColumnIDs) { - // If we found haven't found a replacement, then we check that the drop + // The constraint being deleted could potentially be required by a + // referencing foreign key. Find alternatives if that's the case, + // otherwise remove the foreign key. + if uniqueConstraint.IsValidReferencedUniqueConstraint(fk) && + !uniqueConstraintHasReplacementCandidate(fk) { + // If we haven't found a replacement, then we check that the drop // behavior is cascade. - if err := p.canRemoveFKBackreference(ctx, constraint.GetName(), fk, behavior); err != nil { + if err := p.canRemoveFKBackreference(ctx, uniqueConstraint.GetName(), fk, behavior); err != nil { return err } sliceIdx-- diff --git a/pkg/sql/backfill.go b/pkg/sql/backfill.go index d316be211848..9c5f67f48a08 100644 --- a/pkg/sql/backfill.go +++ b/pkg/sql/backfill.go @@ -295,8 +295,11 @@ func (sc *SchemaChanger) runBackfill(ctx context.Context) error { addedIndexSpans = append(addedIndexSpans, tableDesc.IndexSpan(sc.execCfg.Codec, idx.GetID())) addedIndexes = append(addedIndexes, idx.GetID()) } - } else if c := m.AsConstraint(); c != nil { - isValidating := c.GetConstraintValidity() == descpb.ConstraintValidity_Validating || c.NotNullColumnID() != 0 + } else if c := m.AsConstraintWithoutIndex(); c != nil { + isValidating := c.GetConstraintValidity() == descpb.ConstraintValidity_Validating + if ck := c.AsCheck(); ck != nil && !isValidating { + isValidating = ck.IsNotNullColumnConstraint() + } isSkippingValidation, err := shouldSkipConstraintValidation(tableDesc, c) if err != nil { return err @@ -321,7 +324,7 @@ func (sc *SchemaChanger) runBackfill(ctx context.Context) error { needColumnBackfill = needColumnBackfill || catalog.ColumnNeedsBackfill(col) } else if idx := m.AsIndex(); idx != nil { // no-op. Handled in (*schemaChanger).done by queueing an index gc job. - } else if c := m.AsConstraint(); c != nil { + } else if c := m.AsConstraintWithoutIndex(); c != nil { constraintsToDrop = append(constraintsToDrop, c) } else if m.AsPrimaryKeySwap() != nil || m.AsComputedColumnSwap() != nil || m.AsMaterializedViewRefresh() != nil || m.AsModifyRowLevelTTL() != nil { // The backfiller doesn't need to do anything here. @@ -410,11 +413,11 @@ func shouldSkipConstraintValidation( } // The check constraint on shard column is always on the shard column itself. - if len(check.ColumnIDs) != 1 { + if check.NumReferencedColumns() != 1 { return false, nil } - checkCol, err := tableDesc.FindColumnWithID(check.ColumnIDs[0]) + checkCol, err := tableDesc.FindColumnWithID(check.GetReferencedColumnID(0)) if err != nil { return false, err } @@ -438,7 +441,7 @@ func (sc *SchemaChanger) dropConstraints( fksByBackrefTable := make(map[descpb.ID][]catalog.Constraint) for _, c := range constraints { if fk := c.AsForeignKey(); fk != nil { - id := fk.ReferencedTableID + id := fk.GetReferencedTableID() if id != sc.descID { fksByBackrefTable[id] = append(fksByBackrefTable[id], c) } @@ -477,7 +480,7 @@ func (sc *SchemaChanger) dropConstraints( if def.Name != constraint.GetName() { continue } - backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.ReferencedTableID, txn) + backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.GetReferencedTableID(), txn) if err != nil { return err } @@ -580,7 +583,7 @@ func (sc *SchemaChanger) addConstraints( fksByBackrefTable := make(map[descpb.ID][]catalog.Constraint) for _, c := range constraints { if fk := c.AsForeignKey(); fk != nil { - id := fk.ReferencedTableID + id := fk.GetReferencedTableID() if id != sc.descID { fksByBackrefTable[id] = append(fksByBackrefTable[id], c) } @@ -616,7 +619,7 @@ func (sc *SchemaChanger) addConstraints( } } if !found { - scTable.Checks = append(scTable.Checks, ck) + scTable.Checks = append(scTable.Checks, ck.CheckDesc()) } } else if fk := constraint.AsForeignKey(); fk != nil { var foundExisting bool @@ -639,8 +642,8 @@ func (sc *SchemaChanger) addConstraints( } } if !foundExisting { - scTable.OutboundFKs = append(scTable.OutboundFKs, *fk) - backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.ReferencedTableID, txn) + scTable.OutboundFKs = append(scTable.OutboundFKs, *fk.ForeignKeyDesc()) + backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.GetReferencedTableID(), txn) if err != nil { return err } @@ -653,11 +656,11 @@ func (sc *SchemaChanger) addConstraints( // referenced table. It's possible for the unique index found during // planning to have been dropped in the meantime, since only the // presence of the backreference prevents it. - _, err = tabledesc.FindFKReferencedUniqueConstraint(backrefTable, fk.ReferencedColumnIDs) + _, err = tabledesc.FindFKReferencedUniqueConstraint(backrefTable, fk) if err != nil { return err } - backrefTable.InboundFKs = append(backrefTable.InboundFKs, *fk) + backrefTable.InboundFKs = append(backrefTable.InboundFKs, *fk.ForeignKeyDesc()) // Note that this code may add the same descriptor to the batch // multiple times if it is referenced multiple times. That's fine as @@ -689,7 +692,8 @@ func (sc *SchemaChanger) addConstraints( } } if !found { - scTable.UniqueWithoutIndexConstraints = append(scTable.UniqueWithoutIndexConstraints, *uwi) + scTable.UniqueWithoutIndexConstraints = + append(scTable.UniqueWithoutIndexConstraints, *uwi.UniqueWithoutIndexDesc()) } } } @@ -780,9 +784,9 @@ func (sc *SchemaChanger) validateConstraints( defer func() { collection.ReleaseAll(ctx) }() if ck := c.AsCheck(); ck != nil { if err := validateCheckInTxn( - ctx, &semaCtx, evalCtx.SessionData(), desc, txn, ie, ck.Expr, + ctx, &semaCtx, evalCtx.SessionData(), desc, txn, ie, ck.GetExpr(), ); err != nil { - if c.NotNullColumnID() != 0 { + if ck.IsNotNullColumnConstraint() { // TODO (lucy): This should distinguish between constraint // validation errors and other types of unexpected errors, and // return a different error code in the former case @@ -1518,15 +1522,11 @@ func (e InvalidIndexesError) Error() string { func ValidateCheckConstraint( ctx context.Context, tableDesc catalog.TableDescriptor, - constraint catalog.Constraint, + checkConstraint catalog.CheckConstraint, sessionData *sessiondata.SessionData, runHistoricalTxn descs.HistoricalInternalExecTxnRunner, execOverride sessiondata.InternalExecutorOverride, ) (err error) { - ck := constraint.AsCheck() - if ck == nil { - return errors.AssertionFailedf("%v is not a check constraint", constraint.GetName()) - } tableDesc, err = tableDesc.MakeFirstMutationPublic(catalog.IgnoreConstraints) if err != nil { @@ -1544,7 +1544,7 @@ func ValidateCheckConstraint( defer func() { descriptors.ReleaseAll(ctx) }() return ie.WithSyntheticDescriptors([]catalog.Descriptor{tableDesc}, func() error { - return validateCheckExpr(ctx, &semaCtx, txn, sessionData, ck.Expr, + return validateCheckExpr(ctx, &semaCtx, txn, sessionData, checkConstraint.GetExpr(), tableDesc.(*tabledesc.Mutable), ie) }) }) @@ -2385,7 +2385,7 @@ func runSchemaChangesInTxn( if err := indexBackfillInTxn(ctx, planner.Txn(), planner.EvalContext(), planner.SemaCtx(), immutDesc, traceKV); err != nil { return err } - } else if c := m.AsConstraint(); c != nil { + } else if c := m.AsConstraintWithoutIndex(); c != nil { // This is processed later. Do not proceed to MakeMutationComplete. constraintAdditionMutations = append(constraintAdditionMutations, c) continue @@ -2410,7 +2410,7 @@ func runSchemaChangesInTxn( ); err != nil { return err } - } else if c := m.AsConstraint(); c != nil { + } else if c := m.AsConstraintWithoutIndex(); c != nil { if c.AsCheck() != nil { for i := range tableDesc.Checks { if tableDesc.Checks[i].Name == c.GetName() { @@ -2457,17 +2457,17 @@ func runSchemaChangesInTxn( } if ck := c.AsCheck(); ck != nil { ctu.ConstraintType = descpb.ConstraintToUpdate_CHECK - ctu.Check = *ck - if c.NotNullColumnID() != 0 { + ctu.Check = *ck.CheckDesc() + if ck.IsNotNullColumnConstraint() { ctu.ConstraintType = descpb.ConstraintToUpdate_NOT_NULL - ctu.NotNullColumn = c.NotNullColumnID() + ctu.NotNullColumn = ck.GetReferencedColumnID(0) } } else if fk := c.AsForeignKey(); fk != nil { ctu.ConstraintType = descpb.ConstraintToUpdate_FOREIGN_KEY - ctu.ForeignKey = *fk + ctu.ForeignKey = *fk.ForeignKeyDesc() } else if uwi := c.AsUniqueWithoutIndex(); uwi != nil { ctu.ConstraintType = descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX - ctu.UniqueWithoutIndexConstraint = *uwi + ctu.UniqueWithoutIndexConstraint = *uwi.UniqueWithoutIndexDesc() } else { return errors.AssertionFailedf("unknown constraint type: %s", c) } @@ -2487,13 +2487,13 @@ func runSchemaChangesInTxn( // mutations applied, it can be used for validating check/FK constraints. for _, c := range constraintAdditionMutations { if check := c.AsCheck(); check != nil { - if check.Validity == descpb.ConstraintValidity_Validating { + if check.GetConstraintValidity() == descpb.ConstraintValidity_Validating { if err := planner.WithInternalExecutor(ctx, func(ctx context.Context, txn *kv.Txn, ie sqlutil.InternalExecutor) error { - return validateCheckInTxn(ctx, &planner.semaCtx, planner.SessionData(), tableDesc, txn, ie, check.Expr) + return validateCheckInTxn(ctx, &planner.semaCtx, planner.SessionData(), tableDesc, txn, ie, check.GetExpr()) }); err != nil { return err } - check.Validity = descpb.ConstraintValidity_Validated + check.CheckDesc().Validity = descpb.ConstraintValidity_Validated } } else if fk := c.AsForeignKey(); fk != nil { // We can't support adding a validated foreign key constraint in the same @@ -2508,9 +2508,9 @@ func runSchemaChangesInTxn( // schema change framework eventually. // // For now, just always add the FK as unvalidated. - fk.Validity = descpb.ConstraintValidity_Unvalidated + fk.ForeignKeyDesc().Validity = descpb.ConstraintValidity_Unvalidated } else if uwi := c.AsUniqueWithoutIndex(); uwi != nil { - if uwi.Validity == descpb.ConstraintValidity_Validating { + if uwi.GetConstraintValidity() == descpb.ConstraintValidity_Validating { if err := planner.WithInternalExecutor(ctx, func(ctx context.Context, txn *kv.Txn, ie sqlutil.InternalExecutor) error { return validateUniqueWithoutIndexConstraintInTxn( ctx, @@ -2523,7 +2523,7 @@ func runSchemaChangesInTxn( }); err != nil { return err } - uwi.Validity = descpb.ConstraintValidity_Validated + uwi.UniqueWithoutIndexDesc().Validity = descpb.ConstraintValidity_Validated } } else { return errors.AssertionFailedf("unsupported constraint type: %s", c) @@ -2536,22 +2536,22 @@ func runSchemaChangesInTxn( // update the table descriptor directly. for _, c := range constraintAdditionMutations { if ck := c.AsCheck(); ck != nil { - tableDesc.Checks = append(tableDesc.Checks, ck) + tableDesc.Checks = append(tableDesc.Checks, ck.CheckDesc()) } else if fk := c.AsForeignKey(); fk != nil { var referencedTableDesc *tabledesc.Mutable // We don't want to lookup/edit a second copy of the same table. - selfReference := tableDesc.ID == fk.ReferencedTableID + selfReference := tableDesc.ID == fk.GetReferencedTableID() if selfReference { referencedTableDesc = tableDesc } else { - lookup, err := planner.Descriptors().GetMutableTableVersionByID(ctx, fk.ReferencedTableID, planner.Txn()) + lookup, err := planner.Descriptors().GetMutableTableVersionByID(ctx, fk.GetReferencedTableID(), planner.Txn()) if err != nil { - return errors.Wrapf(err, "error resolving referenced table ID %d", fk.ReferencedTableID) + return errors.Wrapf(err, "error resolving referenced table ID %d", fk.GetReferencedTableID()) } referencedTableDesc = lookup } - referencedTableDesc.InboundFKs = append(referencedTableDesc.InboundFKs, *fk) - tableDesc.OutboundFKs = append(tableDesc.OutboundFKs, *fk) + referencedTableDesc.InboundFKs = append(referencedTableDesc.InboundFKs, *fk.ForeignKeyDesc()) + tableDesc.OutboundFKs = append(tableDesc.OutboundFKs, *fk.ForeignKeyDesc()) // Write the other table descriptor here if it's not the current table // we're already modifying. @@ -2565,7 +2565,7 @@ func runSchemaChangesInTxn( } } } else if uwi := c.AsUniqueWithoutIndex(); uwi != nil { - tableDesc.UniqueWithoutIndexConstraints = append(tableDesc.UniqueWithoutIndexConstraints, *uwi) + tableDesc.UniqueWithoutIndexConstraints = append(tableDesc.UniqueWithoutIndexConstraints, *uwi.UniqueWithoutIndexDesc()) } else { return errors.AssertionFailedf("unsupported constraint type: %s", c) } diff --git a/pkg/sql/backfill_test.go b/pkg/sql/backfill_test.go index 9bf2235a1afb..0e79cd130a72 100644 --- a/pkg/sql/backfill_test.go +++ b/pkg/sql/backfill_test.go @@ -29,11 +29,51 @@ type constraintToUpdateForTest struct { desc *descpb.ConstraintToUpdate } -// Check returns the underlying check constraint, if there is one. -func (c constraintToUpdateForTest) AsCheck() *descpb.TableDescriptor_CheckConstraint { +var _ catalog.CheckConstraint = (*constraintToUpdateForTest)(nil) + +// AsCheck implements the catalog.Constraint interface. +func (c constraintToUpdateForTest) AsCheck() catalog.CheckConstraint { + if c.desc.ConstraintType != descpb.ConstraintToUpdate_CHECK { + return nil + } + return c +} + +// CheckDesc implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) CheckDesc() *descpb.TableDescriptor_CheckConstraint { return &c.desc.Check } +// GetExpr implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) GetExpr() string { + return c.desc.Check.Expr +} + +// NumReferencedColumns implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) NumReferencedColumns() int { + return len(c.desc.Check.ColumnIDs) +} + +// GetReferencedColumnID implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) GetReferencedColumnID(columnOrdinal int) descpb.ColumnID { + return c.desc.Check.ColumnIDs[columnOrdinal] +} + +// CollectReferencedColumnIDs implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) CollectReferencedColumnIDs() catalog.TableColSet { + return catalog.MakeTableColSet(c.desc.Check.ColumnIDs...) +} + +// IsHashShardingConstraint implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) IsHashShardingConstraint() bool { + return c.desc.Check.FromHashShardedColumn +} + +// IsNotNullColumnConstraint implements the catalog.CheckConstraint interface. +func (c constraintToUpdateForTest) IsNotNullColumnConstraint() bool { + return c.desc.NotNullColumn != 0 +} + func TestShouldSkipConstraintValidation(t *testing.T) { defer leaktest.AfterTest(t)() diff --git a/pkg/sql/catalog/descpb/index.go b/pkg/sql/catalog/descpb/index.go index cdf3241c927e..1d2c86c08ada 100644 --- a/pkg/sql/catalog/descpb/index.go +++ b/pkg/sql/catalog/descpb/index.go @@ -64,22 +64,6 @@ func (desc *IndexDescriptor) FillColumns(elems tree.IndexElemList) error { return nil } -// IsHelpfulOriginIndex returns whether the index may be a helpful index for -// performing foreign key checks and cascades for a foreign key with the given -// origin columns. Given the originColIDs for foreign key constraint, the index -// could be useful for FK check if the first column covered by the index is -// present in FK constraint columns. -func (desc *IndexDescriptor) IsHelpfulOriginIndex(originColIDs ColumnIDs) bool { - isHelpfulOriginIndex := len(desc.KeyColumnIDs) > 0 && originColIDs.Contains(desc.KeyColumnIDs[0]) - return !desc.IsPartial() && isHelpfulOriginIndex -} - -// IsValidOriginIndex returns whether the index can serve as an origin index for a foreign -// key constraint with the provided set of originColIDs. -func (desc *IndexDescriptor) IsValidOriginIndex(originColIDs ColumnIDs) bool { - return !desc.IsPartial() && ColumnIDs(desc.KeyColumnIDs).HasPrefix(originColIDs) -} - // explicitColumnIDsWithoutShardColumn returns explicit column ids of the index // excluding the shard column. func (desc *IndexDescriptor) explicitColumnIDsWithoutShardColumn() ColumnIDs { @@ -99,9 +83,9 @@ func (desc *IndexDescriptor) implicitColumnIDs() ColumnIDs { return desc.KeyColumnIDs[:desc.Partitioning.NumImplicitColumns] } -// IsValidReferencedUniqueConstraint is part of the UniqueConstraint interface. -// It returns whether the index can serve as a referenced index for a foreign -// key constraint with the provided set of referencedColumnIDs. +// IsValidReferencedUniqueConstraint returns whether the index can serve +// as a referenced index for a foreign key constraint with the provided set +// of referencedColumnIDs. func (desc *IndexDescriptor) IsValidReferencedUniqueConstraint(referencedColIDs ColumnIDs) bool { explicitColumnIDs := desc.explicitColumnIDsWithoutShardColumn() allColumnIDs := append(explicitColumnIDs, desc.implicitColumnIDs()...) diff --git a/pkg/sql/catalog/descpb/structured.go b/pkg/sql/catalog/descpb/structured.go index adf39be897ba..0811c9860e52 100644 --- a/pkg/sql/catalog/descpb/structured.go +++ b/pkg/sql/catalog/descpb/structured.go @@ -338,34 +338,6 @@ func (DescriptorState) SafeValue() {} // SafeValue implements the redact.SafeValue interface. func (ConstraintType) SafeValue() {} -// UniqueConstraint is an interface for a unique constraint. It allows -// both UNIQUE indexes and UNIQUE WITHOUT INDEX constraints to serve as -// the referenced side of a foreign key constraint. -type UniqueConstraint interface { - // IsValidReferencedUniqueConstraint returns whether the unique constraint can - // serve as a referenced unique constraint for a foreign key constraint with the - // provided set of referencedColumnIDs. - IsValidReferencedUniqueConstraint(referencedColIDs ColumnIDs) bool - - // GetName returns the constraint name. - GetName() string -} - -var _ UniqueConstraint = &UniqueWithoutIndexConstraint{} -var _ UniqueConstraint = &IndexDescriptor{} - -// IsValidReferencedUniqueConstraint is part of the UniqueConstraint interface. -func (u *UniqueWithoutIndexConstraint) IsValidReferencedUniqueConstraint( - referencedColIDs ColumnIDs, -) bool { - return !u.IsPartial() && ColumnIDs(u.ColumnIDs).PermutationOf(referencedColIDs) -} - -// GetName is part of the UniqueConstraint interface. -func (u *UniqueWithoutIndexConstraint) GetName() string { - return u.Name -} - // IsPartial returns true if the constraint is a partial unique constraint. func (u *UniqueWithoutIndexConstraint) IsPartial() bool { return u.Predicate != "" diff --git a/pkg/sql/catalog/descriptor.go b/pkg/sql/catalog/descriptor.go index 805c2f2288b7..c1c5c5253389 100644 --- a/pkg/sql/catalog/descriptor.go +++ b/pkg/sql/catalog/descriptor.go @@ -346,7 +346,7 @@ type TableDescriptor interface { GetPrimaryIndexID() descpb.IndexID // GetPrimaryIndex returns the primary index in the form of a catalog.Index // interface. - GetPrimaryIndex() Index + GetPrimaryIndex() UniqueWithIndexConstraint // IsPartitionAllBy returns whether the table has a PARTITION ALL BY clause. IsPartitionAllBy() bool @@ -632,63 +632,75 @@ type TableDescriptor interface { // It's only non-nil if IsView is true. GetDependsOnTypes() []descpb.ID - // AllConstraints returns all constraints from this table. - // A constraint is considered - // - active if its validity is VALIDATED; - // - inactive if its validity is VALIDATING or UNVALIDATED; - // - dropping if its validity is DROPPING; + // AllConstraints returns all constraints in this table, regardless if + // they're enforced yet or not. The ordering of the constraints within this + // slice is partially defined: + // - constraints are grouped by kind, + // - the order of each kind is undefined, + // - for each kind, the enforced constraints appear first + // - and in the same order as the slice in the table descriptor protobuf; + // Checks, OutboundFKs, etc. + // - followed by the constraint mutations, in the same order as in the + // table descriptor's Mutations slice. AllConstraints() []Constraint - // AllActiveAndInactiveConstraints returns all active and inactive constraints from table. - AllActiveAndInactiveConstraints() []Constraint - // AllActiveConstraints returns all active constraints from table. - AllActiveConstraints() []Constraint - - // GetConstraintInfoWithLookup returns a summary of all constraints on the - // table using the provided function to fetch a TableDescriptor from an ID. - // TODO (xiang): The following two legacy methods (`GetConstraintInfoWithLookup` - // and `GetConstraintInfo`) should be replaced with the methods above that - // retrieve constraints from the table and expose a `Constraint` interface. - GetConstraintInfoWithLookup(fn TableLookupFn) (map[string]descpb.ConstraintDetail, error) - // GetConstraintInfo returns a summary of all constraints on the table. - GetConstraintInfo() (map[string]descpb.ConstraintDetail, error) - // FindConstraintWithID returns a constraint given a constraint id. + // EnforcedConstraints returns the subset of constraints in AllConstraints + // which are enforced for any data written to the table, regardless of + // whether a constraint is valid for table data present before the + // constraint's existence. The order from AllConstraints is preserved. + EnforcedConstraints() []Constraint + + // CheckConstraints returns the subset of check constraints in + // AllConstraints for this table, in the same order. + CheckConstraints() []CheckConstraint + // EnforcedCheckConstraints returns the subset of check constraints in + // EnforcedConstraints for this table, in the same order. + EnforcedCheckConstraints() []CheckConstraint + + // OutboundForeignKeys returns the subset of foreign key constraints in + // AllConstraints for this table, in the same order. + OutboundForeignKeys() []ForeignKeyConstraint + // EnforcedOutboundForeignKeys returns the subset of foreign key constraints + // in EnforcedConstraints for this table, in the same order. + EnforcedOutboundForeignKeys() []ForeignKeyConstraint + // InboundForeignKeys returns all foreign key back-references from this + // table, in the same order as in the InboundFKs slice. + InboundForeignKeys() []ForeignKeyConstraint + + // UniqueConstraintsWithIndex returns the subset of index-backed unique + // constraints in AllConstraints for this table, in the same order. + UniqueConstraintsWithIndex() []UniqueWithIndexConstraint + // EnforcedUniqueConstraintsWithIndex returns the subset of index-backed + // unique constraints in EnforcedConstraints for this table, in the same + // order. + EnforcedUniqueConstraintsWithIndex() []UniqueWithIndexConstraint + + // UniqueConstraintsWithoutIndex returns the subset of non-index-backed + // unique constraints in AllConstraints for this table, in the same order. + UniqueConstraintsWithoutIndex() []UniqueWithoutIndexConstraint + // EnforcedUniqueConstraintsWithoutIndex returns the subset of + // non-index-backed unique constraints in EnforcedConstraints for this table, + // in the same order. + EnforcedUniqueConstraintsWithoutIndex() []UniqueWithoutIndexConstraint + + // FindConstraintWithID traverses the slice returned by AllConstraints and + // returns the first catalog.Constraint that matches the desired ID, or an + // error if none was found. FindConstraintWithID(id descpb.ConstraintID) (Constraint, error) - - // GetUniqueWithoutIndexConstraints returns all the unique constraints defined - // on this table that are not enforced by an index. - GetUniqueWithoutIndexConstraints() []descpb.UniqueWithoutIndexConstraint - // AllActiveAndInactiveUniqueWithoutIndexConstraints returns all unique - // constraints that are not enforced by an index, including both "active" - // ones on the table descriptor which are being enforced for all writes, and - // "inactive" ones queued in the mutations list. - AllActiveAndInactiveUniqueWithoutIndexConstraints() []*descpb.UniqueWithoutIndexConstraint - - // ForeachOutboundFK calls f for every outbound foreign key in desc until an - // error is returned. - ForeachOutboundFK(f func(fk *descpb.ForeignKeyConstraint) error) error - // ForeachInboundFK calls f for every inbound foreign key in desc until an - // error is returned. - ForeachInboundFK(f func(fk *descpb.ForeignKeyConstraint) error) error - // AllActiveAndInactiveForeignKeys returns all foreign keys, including both - // "active" ones on the index descriptor which are being enforced for all - // writes, and "inactive" ones queued in the mutations list. An error is - // returned if multiple foreign keys (including mutations) are found for the - // same index. - AllActiveAndInactiveForeignKeys() []*descpb.ForeignKeyConstraint - - // GetChecks returns information about this table's check constraints, if - // there are any. Only valid if IsTable returns true. - GetChecks() []*descpb.TableDescriptor_CheckConstraint - // AllActiveAndInactiveChecks returns all check constraints, including both - // "active" ones on the table descriptor which are being enforced for all - // writes, and "inactive" new checks constraints queued in the mutations list. - // Additionally, if there are any dropped mutations queued inside the mutation - // list, those will not cancel any "active" or "inactive" mutations. - AllActiveAndInactiveChecks() []*descpb.TableDescriptor_CheckConstraint - // ActiveChecks returns a list of all check constraints that should be enforced - // on writes (including constraints being added/validated). The columns - // referenced by the returned checks are writable, but not necessarily public. - ActiveChecks() []descpb.TableDescriptor_CheckConstraint + // FindConstraintWithName is like FindConstraintWithID but for names. + FindConstraintWithName(name string) (Constraint, error) + + // CheckConstraintColumns returns the slice of columns referenced by a check + // constraint. + CheckConstraintColumns(ck CheckConstraint) []Column + // ForeignKeyReferencedColumns returns the slice of columns referenced by an + // inbound foreign key. + ForeignKeyReferencedColumns(fk ForeignKeyConstraint) []Column + // ForeignKeyOriginColumns returns the slice of columns originating in this + // table for an outbound foreign key. + ForeignKeyOriginColumns(fk ForeignKeyConstraint) []Column + // UniqueWithoutIndexColumns returns the slice of columns which are + // defined as unique by a non-index-backed constraint. + UniqueWithoutIndexColumns(uwoi UniqueWithoutIndexConstraint) []Column // GetLocalityConfig returns the locality config for this table, which // describes the table's multi-region locality policy if one is set (e.g. diff --git a/pkg/sql/catalog/post_deserialization_changes.go b/pkg/sql/catalog/post_deserialization_changes.go index 252ac83f0d55..2f149f299a31 100644 --- a/pkg/sql/catalog/post_deserialization_changes.go +++ b/pkg/sql/catalog/post_deserialization_changes.go @@ -102,4 +102,8 @@ const ( // SetSystemDatabaseDescriptorVersion indicates that the system database // descriptor did not have its version set. SetSystemDatabaseDescriptorVersion + + // SetCheckConstraintColumnIDs indicates that a table's check constraint's + // ColumnIDs slice hadn't been set yet, and was set to a non-empty slice. + SetCheckConstraintColumnIDs ) diff --git a/pkg/sql/catalog/table_elements.go b/pkg/sql/catalog/table_elements.go index b2bd5a657394..c9a9152482e7 100644 --- a/pkg/sql/catalog/table_elements.go +++ b/pkg/sql/catalog/table_elements.go @@ -60,9 +60,31 @@ type TableElementMaybeMutation interface { Dropped() bool } +// ConstraintProvider is an interface for something which might unwrap +// a constraint. +type ConstraintProvider interface { + + // AsCheck returns the corresponding CheckConstraint if there is one, + // nil otherwise. + AsCheck() CheckConstraint + + // AsForeignKey returns the corresponding ForeignKeyConstraint if + // there is one, nil otherwise. + AsForeignKey() ForeignKeyConstraint + + // AsUniqueWithIndex returns the corresponding UniqueWithIndexConstraint if + // there is one, nil otherwise. + AsUniqueWithIndex() UniqueWithIndexConstraint + + // AsUniqueWithoutIndex returns the corresponding + // UniqueWithoutIndexConstraint if there is one, nil otherwise. + AsUniqueWithoutIndex() UniqueWithoutIndexConstraint +} + // Mutation is an interface around a table descriptor mutation. type Mutation interface { TableElementMaybeMutation + ConstraintProvider // AsColumn returns the corresponding Column if the mutation is on a column, // nil otherwise. @@ -72,9 +94,10 @@ type Mutation interface { // nil otherwise. AsIndex() Index - // AsConstraint returns the corresponding Constraint if the mutation - // is on a constraint, nil otherwise. - AsConstraint() Constraint + // AsConstraintWithoutIndex returns the corresponding WithoutIndexConstraint + // if the mutation is on a check constraint or on a foreign key constraint or + // on a non-index-backed unique constraint, nil otherwise. + AsConstraintWithoutIndex() WithoutIndexConstraint // AsPrimaryKeySwap returns the corresponding PrimaryKeySwap if the mutation // is a primary key swap, nil otherwise. @@ -104,6 +127,7 @@ type Mutation interface { // Index is an interface around the index descriptor types. type Index interface { TableElementMaybeMutation + ConstraintProvider // IndexDesc returns the underlying protobuf descriptor. // Ideally, this method should be called as rarely as possible. @@ -152,9 +176,9 @@ type Index interface { GetSharded() catpb.ShardedDescriptor GetShardColumnName() string - IsValidOriginIndex(originColIDs descpb.ColumnIDs) bool - IsHelpfulOriginIndex(originColIDs descpb.ColumnIDs) bool - IsValidReferencedUniqueConstraint(referencedColIDs descpb.ColumnIDs) bool + // IsValidOriginIndex returns whether the index can serve as an origin index + // for a foreign key constraint. + IsValidOriginIndex(fk ForeignKeyConstraint) bool GetPartitioning() Partitioning PartitioningColumnCount() int @@ -238,7 +262,7 @@ type Index interface { // was issued. CreatedAt() time.Time - // IsTemporaryIndexForBackfill() returns true iff the index is + // IsTemporaryIndexForBackfill returns true iff the index is // an index being used as the temporary index being used by an // in-progress index backfill. IsTemporaryIndexForBackfill() bool @@ -400,6 +424,7 @@ type Column interface { // Constraint is an interface around a constraint. type Constraint interface { TableElementMaybeMutation + ConstraintProvider fmt.Stringer // GetConstraintID returns the ID for the constraint. @@ -408,30 +433,157 @@ type Constraint interface { // GetConstraintValidity returns the validity of this constraint. GetConstraintValidity() descpb.ConstraintValidity + // IsEnforced returns true iff the constraint is enforced for all writes to + // the parent table. + IsEnforced() bool + // GetName returns the name of this constraint update mutation. GetName() string - // NotNullColumnID returns the underlying not-null column ID, if there is one. - NotNullColumnID() descpb.ColumnID + // IsConstraintValidated returns true iff the constraint is enforced for + // all writes to the table data and has also been validated on the table's + // existing data prior to the addition of the constraint, if there was any. + IsConstraintValidated() bool + + // IsConstraintUnvalidated returns true iff the constraint is enforced for + // all writes to the table data but which is explicitly NOT to be validated + // on any data in the table prior to the addition of the constraint. + IsConstraintUnvalidated() bool +} + +// UniqueConstraint is an interface for a unique constraint. +// These are either backed by an index, or not, see respectively +// UniqueWithIndexConstraint and UniqueWithoutIndexConstraint. +type UniqueConstraint interface { + Constraint + + // IsValidReferencedUniqueConstraint returns whether the unique constraint can + // serve as a referenced unique constraint for a foreign key constraint. + IsValidReferencedUniqueConstraint(fk ForeignKeyConstraint) bool + + // NumKeyColumns returns the number of columns in this unique constraint. + NumKeyColumns() int + + // GetKeyColumnID returns the ID of the column in the unique constraint at + // ordinal `columnOrdinal`. + GetKeyColumnID(columnOrdinal int) descpb.ColumnID + + // CollectKeyColumnIDs returns the columns in the unique constraint in a new + // TableColSet. + CollectKeyColumnIDs() TableColSet + + // IsPartial returns true iff this is a partial uniqueness constraint. + IsPartial() bool + + // GetPredicate returns the partial predicate if there is one, "" otherwise. + GetPredicate() string +} + +// UniqueWithIndexConstraint is an interface around a unique constraint +// which backed by an index. +type UniqueWithIndexConstraint interface { + UniqueConstraint + Index +} + +// WithoutIndexConstraint is the supertype of all constraint subtypes which are +// not backed by an index. +type WithoutIndexConstraint interface { + Constraint +} + +// CheckConstraint is an interface around a check constraint. +type CheckConstraint interface { + WithoutIndexConstraint + + // CheckDesc returns the underlying descriptor protobuf. + CheckDesc() *descpb.TableDescriptor_CheckConstraint + + // GetExpr returns the check expression as a string. + GetExpr() string + + // NumReferencedColumns returns the number of column references in the check + // expression. Note that a column may be referenced multiple times in an + // expression; the number returned here is the number of references, not the + // number of distinct columns. + NumReferencedColumns() int + + // GetReferencedColumnID returns the ID of the column referenced in the check + // expression at ordinal `columnOrdinal`. + GetReferencedColumnID(columnOrdinal int) descpb.ColumnID + + // CollectReferencedColumnIDs returns the columns referenced in the check + // constraint expression in a new TableColSet. + CollectReferencedColumnIDs() TableColSet + + // IsNotNullColumnConstraint returns true iff this check constraint is a + // NOT NULL on a column. + IsNotNullColumnConstraint() bool + + // IsHashShardingConstraint returns true iff this check constraint is + // associated with a hash-sharding column in this table. + IsHashShardingConstraint() bool +} - // AsCheck returns the underlying check constraint, if there is one. - AsCheck() *descpb.TableDescriptor_CheckConstraint +// ForeignKeyConstraint is an interface around a check constraint. +type ForeignKeyConstraint interface { + WithoutIndexConstraint - // AsForeignKey returns the underlying foreign key constraint, if there is - // one. - AsForeignKey() *descpb.ForeignKeyConstraint + // ForeignKeyDesc returns the underlying descriptor protobuf. + ForeignKeyDesc() *descpb.ForeignKeyConstraint + + // GetOriginTableID returns the ID of the table at the origin of this foreign + // key. + GetOriginTableID() descpb.ID + + // NumOriginColumns returns the number of origin columns in the foreign key. + NumOriginColumns() int + + // GetOriginColumnID returns the ID of the origin column in the foreign + // key at ordinal `columnOrdinal`. + GetOriginColumnID(columnOrdinal int) descpb.ColumnID + + // CollectOriginColumnIDs returns the origin columns in the foreign key + // in a new TableColSet. + CollectOriginColumnIDs() TableColSet + + // GetReferencedTableID returns the ID of the table referenced by this + // foreign key. + GetReferencedTableID() descpb.ID + + // NumReferencedColumns returns the number of columns referenced by this + // foreign key. + NumReferencedColumns() int + + // GetReferencedColumnID returns the ID of the column referenced by the + // foreign key at ordinal `columnOrdinal`. + GetReferencedColumnID(columnOrdinal int) descpb.ColumnID + + // CollectReferencedColumnIDs returns the columns referenced by the foreign + // key in a new TableColSet. + CollectReferencedColumnIDs() TableColSet + + // OnDelete returns the action to take ON DELETE. + OnDelete() catpb.ForeignKeyAction + + // OnUpdate returns the action to take ON UPDATE. + OnUpdate() catpb.ForeignKeyAction + + // Match returns the type of algorithm used to match composite keys. + Match() descpb.ForeignKeyReference_Match +} - // AsUniqueWithoutIndex returns the underlying unique without index - // constraint, if there is one. - AsUniqueWithoutIndex() *descpb.UniqueWithoutIndexConstraint +// UniqueWithoutIndexConstraint is an interface around a unique constraint +// which is not backed by an index. +type UniqueWithoutIndexConstraint interface { + WithoutIndexConstraint + UniqueConstraint - // AsPrimaryKey returns the index descriptor backing the PRIMARY KEY - // constraint, if there is one. - AsPrimaryKey() Index + // UniqueWithoutIndexDesc returns the underlying descriptor protobuf. + UniqueWithoutIndexDesc() *descpb.UniqueWithoutIndexConstraint - // AsUnique returns the index descriptor backing the UNIQUE constraint, - // if there is one. - AsUnique() Index + // ParentTableID returns the ID of the table this constraint applies to. + ParentTableID() descpb.ID } // PrimaryKeySwap is an interface around a primary key swap mutation. diff --git a/pkg/sql/catalog/tabledesc/BUILD.bazel b/pkg/sql/catalog/tabledesc/BUILD.bazel index 402afb2661df..f97c58488043 100644 --- a/pkg/sql/catalog/tabledesc/BUILD.bazel +++ b/pkg/sql/catalog/tabledesc/BUILD.bazel @@ -26,7 +26,6 @@ go_library( "//pkg/keys", "//pkg/roachpb", "//pkg/settings", - "//pkg/settings/cluster", "//pkg/sql/catalog", "//pkg/sql/catalog/catpb", "//pkg/sql/catalog/catprivilege", diff --git a/pkg/sql/catalog/tabledesc/constraint.go b/pkg/sql/catalog/tabledesc/constraint.go index 4cc53761d723..ff14d97dd321 100644 --- a/pkg/sql/catalog/tabledesc/constraint.go +++ b/pkg/sql/catalog/tabledesc/constraint.go @@ -11,215 +11,465 @@ package tabledesc import ( + "fmt" + "github.com/cockroachdb/cockroach/pkg/sql/catalog" + "github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" - "github.com/cockroachdb/errors" + "github.com/cockroachdb/cockroach/pkg/util" ) -// constraintCache contains precomputed slices of constraints, categorized by kind and validity. -// A constraint is considered -// - active if its validity is VALIDATED; -// - inactive if its validity is VALIDATING or UNVALIDATED; -// - dropping if its validity is DROPPING; -type constraintCache struct { - all []catalog.Constraint - allActiveAndInactive []catalog.Constraint - allActive []catalog.Constraint +type constraintBase struct { + maybeMutation +} - allChecks []catalog.Constraint - allActiveAndInactiveChecks []catalog.Constraint - allActiveChecks []catalog.Constraint +// AsCheck implements the catalog.ConstraintProvider interface. +func (c constraintBase) AsCheck() catalog.CheckConstraint { + return nil +} - allNotNulls []catalog.Constraint - allActiveAndInactiveNotNulls []catalog.Constraint - allActiveNotNulls []catalog.Constraint +// AsForeignKey implements the catalog.ConstraintProvider interface. +func (c constraintBase) AsForeignKey() catalog.ForeignKeyConstraint { + return nil +} - allFKs []catalog.Constraint - allActiveAndInactiveFKs []catalog.Constraint - allActiveFKs []catalog.Constraint +// AsUniqueWithoutIndex implements the catalog.ConstraintProvider interface. +func (c constraintBase) AsUniqueWithoutIndex() catalog.UniqueWithoutIndexConstraint { + return nil +} - allUniqueWithoutIndexes []catalog.Constraint - allActiveAndInactiveUniqueWithoutIndexes []catalog.Constraint - allActiveUniqueWithoutIndexes []catalog.Constraint +// AsUniqueWithIndex implements the catalog.ConstraintProvider interface. +func (c constraintBase) AsUniqueWithIndex() catalog.UniqueWithIndexConstraint { + return nil } -// newConstraintCache returns a fresh fully-populated constraintCache struct for the -// TableDescriptor. -func newConstraintCache(desc *descpb.TableDescriptor, mutations *mutationCache) *constraintCache { - c := constraintCache{} - - // addIfNotExists is a function that adds constraint `c` to slice `dest` if this - // constraint is not already in it (as determined by using a set of already added - // constraint IDs `constraintIDsInDest`). - // If `constraintIDsInDest` is nil, blindly append `c` to `dest`. - addIfNotExists := func( - c catalog.Constraint, - dest []catalog.Constraint, - constraintIDsInDest map[descpb.ConstraintID]bool, - ) []catalog.Constraint { - if constraintIDsInDest == nil { - dest = append(dest, c) - return dest - } +type checkConstraint struct { + constraintBase + desc *descpb.TableDescriptor_CheckConstraint +} - if _, exist := constraintIDsInDest[c.GetConstraintID()]; exist { - return dest - } - dest = append(dest, c) - constraintIDsInDest[c.GetConstraintID()] = true - return dest - } +var _ catalog.CheckConstraint = (*checkConstraint)(nil) - // addConstraintToSetsByValidity is a function that adds constraint `c` to various slice - // categorized by validity. - addConstraintToSetsByValidity := func( - c catalog.Constraint, - all, allActiveAndInactive, allActive []catalog.Constraint, - ) ( - updatedAll []catalog.Constraint, - updatedAllActiveAndInactive []catalog.Constraint, - updatedAllActive []catalog.Constraint, - ) { - cstValidity := c.GetConstraintValidity() - all = addIfNotExists(c, all, nil) - - if cstValidity == descpb.ConstraintValidity_Validated || - cstValidity == descpb.ConstraintValidity_Validating || - cstValidity == descpb.ConstraintValidity_Unvalidated { - allActiveAndInactive = addIfNotExists(c, allActiveAndInactive, nil) - } - if cstValidity == descpb.ConstraintValidity_Validated { - allActive = addIfNotExists(c, allActive, nil) - } - return all, allActiveAndInactive, allActive - } +// CheckDesc implements the catalog.CheckConstraint interface. +func (c checkConstraint) CheckDesc() *descpb.TableDescriptor_CheckConstraint { + return c.desc +} - // Glean all constraints from `desc` and `mutations`. - var allConstraints []catalog.Constraint - constraintIDs := make(map[descpb.ConstraintID]bool) - for _, cst := range []descpb.IndexDescriptor{desc.PrimaryIndex} { - backingStruct := index{ - desc: &cst, - validityIfConstraint: descpb.ConstraintValidity_Validated, - } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) +// Expr implements the catalog.CheckConstraint interface. +func (c checkConstraint) GetExpr() string { + return c.desc.Expr +} + +// NumReferencedColumns implements the catalog.CheckConstraint interface. +func (c checkConstraint) NumReferencedColumns() int { + return len(c.desc.ColumnIDs) +} + +// GetReferencedColumnID implements the catalog.CheckConstraint +// interface. +func (c checkConstraint) GetReferencedColumnID(columnOrdinal int) descpb.ColumnID { + return c.desc.ColumnIDs[columnOrdinal] +} + +// CollectReferencedColumnIDs implements the catalog.CheckConstraint +// interface. +func (c checkConstraint) CollectReferencedColumnIDs() catalog.TableColSet { + return catalog.MakeTableColSet(c.desc.ColumnIDs...) +} + +// IsNotNullColumnConstraint implements the catalog.CheckConstraint interface. +func (c checkConstraint) IsNotNullColumnConstraint() bool { + return c.desc.IsNonNullConstraint +} + +// IsHashShardingConstraint implements the catalog.CheckConstraint interface. +func (c checkConstraint) IsHashShardingConstraint() bool { + return c.desc.FromHashShardedColumn +} + +// GetConstraintID implements the catalog.Constraint interface. +func (c checkConstraint) GetConstraintID() descpb.ConstraintID { + return c.desc.ConstraintID +} + +// GetConstraintValidity implements the catalog.Constraint interface. +func (c checkConstraint) GetConstraintValidity() descpb.ConstraintValidity { + return c.desc.Validity +} + +// IsConstraintValidated implements the catalog.Constraint interface. +func (c checkConstraint) IsConstraintValidated() bool { + return c.desc.Validity == descpb.ConstraintValidity_Validated +} + +// IsConstraintUnvalidated implements the catalog.Constraint interface. +func (c checkConstraint) IsConstraintUnvalidated() bool { + return c.desc.Validity == descpb.ConstraintValidity_Unvalidated +} + +// GetName implements the catalog.Constraint interface. +func (c checkConstraint) GetName() string { + return c.desc.Name +} + +// AsCheck implements the catalog.ConstraintProvider interface. +func (c checkConstraint) AsCheck() catalog.CheckConstraint { + return &c +} + +// String implements the catalog.Constraint interface. +func (c checkConstraint) String() string { + return fmt.Sprintf("%+v", c.desc) +} + +// IsEnforced implements the catalog.Constraint interface. +func (c checkConstraint) IsEnforced() bool { + return !c.IsMutation() || c.WriteAndDeleteOnly() +} + +type uniqueWithoutIndexConstraint struct { + constraintBase + desc *descpb.UniqueWithoutIndexConstraint +} + +var _ catalog.UniqueWithoutIndexConstraint = (*uniqueWithoutIndexConstraint)(nil) + +// UniqueWithoutIndexDesc implements the catalog.UniqueWithoutIndexConstraint +// interface. +func (c uniqueWithoutIndexConstraint) UniqueWithoutIndexDesc() *descpb.UniqueWithoutIndexConstraint { + return c.desc +} + +// ParentTableID implements the catalog.UniqueWithoutIndexConstraint +// interface. +func (c uniqueWithoutIndexConstraint) ParentTableID() descpb.ID { + return c.desc.TableID +} + +// IsValidReferencedUniqueConstraint implements the catalog.UniqueConstraint +// interface. +func (c uniqueWithoutIndexConstraint) IsValidReferencedUniqueConstraint( + fk catalog.ForeignKeyConstraint, +) bool { + return !c.IsPartial() && descpb.ColumnIDs(c.desc.ColumnIDs).PermutationOf(fk.ForeignKeyDesc().ReferencedColumnIDs) +} + +// NumKeyColumns implements the catalog.UniqueConstraint interface. +func (c uniqueWithoutIndexConstraint) NumKeyColumns() int { + return len(c.desc.ColumnIDs) +} + +// GetKeyColumnID implements the catalog.UniqueConstraint interface. +func (c uniqueWithoutIndexConstraint) GetKeyColumnID(columnOrdinal int) descpb.ColumnID { + return c.desc.ColumnIDs[columnOrdinal] +} + +// CollectKeyColumnIDs implements the catalog.UniqueConstraint +// interface. +func (c uniqueWithoutIndexConstraint) CollectKeyColumnIDs() catalog.TableColSet { + return catalog.MakeTableColSet(c.desc.ColumnIDs...) +} + +// IsPartial implements the catalog.UniqueConstraint interface. +func (c uniqueWithoutIndexConstraint) IsPartial() bool { + return c.desc.Predicate != "" +} + +// GetPredicate implements the catalog.UniqueConstraint interface. +func (c uniqueWithoutIndexConstraint) GetPredicate() string { + return c.desc.Predicate +} + +// GetConstraintID implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) GetConstraintID() descpb.ConstraintID { + return c.desc.ConstraintID +} + +// GetConstraintValidity implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) GetConstraintValidity() descpb.ConstraintValidity { + return c.desc.Validity +} + +// IsConstraintValidated implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) IsConstraintValidated() bool { + return c.desc.Validity == descpb.ConstraintValidity_Validated +} + +// IsConstraintUnvalidated implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) IsConstraintUnvalidated() bool { + return c.desc.Validity == descpb.ConstraintValidity_Unvalidated +} + +// GetName implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) GetName() string { + return c.desc.Name +} + +// AsUniqueWithoutIndex implements the catalog.ConstraintProvider interface. +func (c uniqueWithoutIndexConstraint) AsUniqueWithoutIndex() catalog.UniqueWithoutIndexConstraint { + return &c +} + +// String implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) String() string { + return fmt.Sprintf("%+v", c.desc) +} + +// IsEnforced implements the catalog.Constraint interface. +func (c uniqueWithoutIndexConstraint) IsEnforced() bool { + return !c.IsMutation() || c.WriteAndDeleteOnly() +} + +type foreignKeyConstraint struct { + constraintBase + desc *descpb.ForeignKeyConstraint +} + +var _ catalog.ForeignKeyConstraint = (*foreignKeyConstraint)(nil) + +// ForeignKeyDesc implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) ForeignKeyDesc() *descpb.ForeignKeyConstraint { + return c.desc +} + +// GetOriginTableID implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) GetOriginTableID() descpb.ID { + return c.desc.OriginTableID +} + +// NumOriginColumns implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) NumOriginColumns() int { + return len(c.desc.OriginColumnIDs) +} + +// GetOriginColumnID implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) GetOriginColumnID(columnOrdinal int) descpb.ColumnID { + return c.desc.OriginColumnIDs[columnOrdinal] +} + +// CollectOriginColumnIDs implements the catalog.ForeignKeyConstraint +// interface. +func (c foreignKeyConstraint) CollectOriginColumnIDs() catalog.TableColSet { + return catalog.MakeTableColSet(c.desc.OriginColumnIDs...) +} + +// GetReferencedTableID implements the catalog.ForeignKeyConstraint +// interface. +func (c foreignKeyConstraint) GetReferencedTableID() descpb.ID { + return c.desc.ReferencedTableID +} + +// NumReferencedColumns implements the catalog.ForeignKeyConstraint +// interface. +func (c foreignKeyConstraint) NumReferencedColumns() int { + return len(c.desc.ReferencedColumnIDs) +} + +// GetReferencedColumnID implements the catalog.ForeignKeyConstraint +// interface. +func (c foreignKeyConstraint) GetReferencedColumnID(columnOrdinal int) descpb.ColumnID { + return c.desc.ReferencedColumnIDs[columnOrdinal] +} + +// CollectReferencedColumnIDs implements the catalog.ForeignKeyConstraint +// interface. +func (c foreignKeyConstraint) CollectReferencedColumnIDs() catalog.TableColSet { + return catalog.MakeTableColSet(c.desc.ReferencedColumnIDs...) +} + +// OnDelete implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) OnDelete() catpb.ForeignKeyAction { + return c.desc.OnDelete +} + +// OnUpdate implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) OnUpdate() catpb.ForeignKeyAction { + return c.desc.OnUpdate +} + +// Match implements the catalog.ForeignKeyConstraint interface. +func (c foreignKeyConstraint) Match() descpb.ForeignKeyReference_Match { + return c.desc.Match +} + +// GetConstraintID implements the catalog.Constraint interface. +func (c foreignKeyConstraint) GetConstraintID() descpb.ConstraintID { + return c.desc.ConstraintID +} + +// GetConstraintValidity implements the catalog.Constraint interface. +func (c foreignKeyConstraint) GetConstraintValidity() descpb.ConstraintValidity { + return c.desc.Validity +} + +// IsConstraintValidated implements the catalog.Constraint interface. +func (c foreignKeyConstraint) IsConstraintValidated() bool { + return c.desc.Validity == descpb.ConstraintValidity_Validated +} + +// IsConstraintUnvalidated implements the catalog.Constraint interface. +func (c foreignKeyConstraint) IsConstraintUnvalidated() bool { + return c.desc.Validity == descpb.ConstraintValidity_Unvalidated +} + +// GetName implements the catalog.Constraint interface. +func (c foreignKeyConstraint) GetName() string { + return c.desc.Name +} + +// AsForeignKey implements the catalog.ConstraintProvider interface. +func (c foreignKeyConstraint) AsForeignKey() catalog.ForeignKeyConstraint { + return &c +} + +// String implements the catalog.Constraint interface. +func (c foreignKeyConstraint) String() string { + return fmt.Sprintf("%+v", c.desc) +} + +// IsEnforced implements the catalog.Constraint interface. +func (c foreignKeyConstraint) IsEnforced() bool { + return !c.IsMutation() || c.WriteAndDeleteOnly() +} + +// constraintCache contains precomputed slices of constraints, categorized by: +// - constraint subtype: checks, fks, etc. +// - enforcement status: whether a constraint is enforced for data written to +// the table, regardless of whether the constraint is valid for table data +// which existed prior to the constraint being added to the table. +type constraintCache struct { + all, allEnforced []catalog.Constraint + checks, checksEnforced []catalog.CheckConstraint + fks, fksEnforced []catalog.ForeignKeyConstraint + uwis, uwisEnforced []catalog.UniqueWithIndexConstraint + uwois, uwoisEnforced []catalog.UniqueWithoutIndexConstraint + fkBackRefs []catalog.ForeignKeyConstraint +} + +// newConstraintCache returns a fresh fully-populated constraintCache struct for the +// TableDescriptor. +func newConstraintCache( + desc *descpb.TableDescriptor, indexes *indexCache, mutations *mutationCache, +) *constraintCache { + capUWIs := len(indexes.all) + numEnforcedChecks := len(desc.Checks) + capChecks := numEnforcedChecks + len(mutations.checks) + numEnforcedFKs := len(desc.OutboundFKs) + capFKs := numEnforcedFKs + len(mutations.fks) + numEnforcedUWOIs := len(desc.UniqueWithoutIndexConstraints) + capUWOIs := numEnforcedUWOIs + len(mutations.uniqueWithoutIndexes) + capAll := capChecks + capFKs + capUWOIs + capUWIs + // Pre-allocate slices which are known not to be empty: + // physical tables always have at least one index-backed unique constraint + // in the form of the primary key. + c := constraintCache{ + all: make([]catalog.Constraint, 0, capAll), + allEnforced: make([]catalog.Constraint, 0, capAll), + uwis: make([]catalog.UniqueWithIndexConstraint, 0, capUWIs), + uwisEnforced: make([]catalog.UniqueWithIndexConstraint, 0, capUWIs), } - for _, cst := range desc.Indexes { - if cst.Unique { - backingStruct := index{ - desc: &cst, - validityIfConstraint: descpb.ConstraintValidity_Validated, + // Populate with index-backed unique constraints. + for _, idx := range indexes.all { + if uwi := idx.AsUniqueWithIndex(); uwi != nil && uwi.NumKeyColumns() > 0 { + c.all = append(c.all, uwi) + c.uwis = append(c.uwis, uwi) + if uwi.IsEnforced() { + c.allEnforced = append(c.allEnforced, uwi) + c.uwisEnforced = append(c.uwisEnforced, uwi) } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) - } - } - for _, cst := range desc.Checks { - backingStruct := constraint{ - desc: &descpb.ConstraintToUpdate{ - ConstraintType: descpb.ConstraintToUpdate_CHECK, - Name: cst.Name, - Check: *cst, - }, } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) } - for _, cst := range append(desc.OutboundFKs, desc.InboundFKs...) { - backingStruct := constraint{ - desc: &descpb.ConstraintToUpdate{ - ConstraintType: descpb.ConstraintToUpdate_FOREIGN_KEY, - Name: cst.Name, - ForeignKey: cst, - }, + // Populate with check constraints. + if capChecks > 0 { + c.checks = make([]catalog.CheckConstraint, 0, capChecks) + var byID util.FastIntMap + var checkBackingStructs []checkConstraint + if numEnforcedChecks > 0 { + checkBackingStructs = make([]checkConstraint, numEnforcedChecks) + for i, ckDesc := range desc.Checks { + checkBackingStructs[i].desc = ckDesc + ck := &checkBackingStructs[i] + byID.Set(int(ck.desc.ConstraintID), i) + c.all = append(c.all, ck) + c.allEnforced = append(c.allEnforced, ck) + c.checks = append(c.checks, ck) + c.checksEnforced = append(c.checksEnforced, ck) + } } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) - } - for _, cst := range desc.UniqueWithoutIndexConstraints { - backingStruct := constraint{ - desc: &descpb.ConstraintToUpdate{ - ConstraintType: descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX, - Name: cst.Name, - UniqueWithoutIndexConstraint: cst, - }, + for _, m := range mutations.checks { + ck := m.AsCheck() + if ordinal, found := byID.Get(int(ck.GetConstraintID())); found { + checkBackingStructs[ordinal].maybeMutation = ck.(*checkConstraint).maybeMutation + } else { + c.all = append(c.all, ck) + c.checks = append(c.checks, ck) + } } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) } - for _, cstMutation := range mutations.all { - if cst := cstMutation.AsConstraint(); cst != nil { - backingStruct := constraint{ - maybeMutation: maybeMutation{ - mutationID: cstMutation.MutationID(), - mutationDirection: mutationDirection(cstMutation), - mutationState: mutationState(cstMutation), - mutationIsRollback: cstMutation.IsRollback(), - }, + // Populate with foreign key constraints. + if capFKs > 0 { + c.fks = make([]catalog.ForeignKeyConstraint, 0, capFKs) + var byID util.FastIntMap + var fkBackingStructs []foreignKeyConstraint + if numEnforcedFKs > 0 { + fkBackingStructs = make([]foreignKeyConstraint, numEnforcedFKs) + for i := range desc.OutboundFKs { + fkBackingStructs[i].desc = &desc.OutboundFKs[i] + fk := &fkBackingStructs[i] + byID.Set(int(fk.desc.ConstraintID), i) + c.all = append(c.all, fk) + c.allEnforced = append(c.allEnforced, fk) + c.fks = append(c.fks, fk) + c.fksEnforced = append(c.fksEnforced, fk) } - if cc, ok := cst.(*constraint); ok { - backingStruct.desc = cc.desc + } + for _, m := range mutations.fks { + fk := m.AsForeignKey() + if ordinal, found := byID.Get(int(fk.GetConstraintID())); found { + fkBackingStructs[ordinal].maybeMutation = fk.(*foreignKeyConstraint).maybeMutation + } else { + c.all = append(c.all, fk) + c.fks = append(c.fks, fk) } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) } - if cst := cstMutation.AsIndex(); cst != nil { - validity := descpb.ConstraintValidity_Validating - if !cstMutation.Adding() { - validity = descpb.ConstraintValidity_Dropping + } + // Populate with non-index-backed unique constraints. + if capUWOIs > 0 { + c.uwois = make([]catalog.UniqueWithoutIndexConstraint, 0, capUWOIs) + var byID util.FastIntMap + var uwoisBackingStructs []uniqueWithoutIndexConstraint + if numEnforcedUWOIs > 0 { + uwoisBackingStructs = make([]uniqueWithoutIndexConstraint, numEnforcedUWOIs) + for i := range desc.UniqueWithoutIndexConstraints { + uwoisBackingStructs[i].desc = &desc.UniqueWithoutIndexConstraints[i] + uwoi := &uwoisBackingStructs[i] + byID.Set(int(uwoi.desc.ConstraintID), i) + c.all = append(c.all, uwoi) + c.allEnforced = append(c.allEnforced, uwoi) + c.uwois = append(c.uwois, uwoi) + c.uwoisEnforced = append(c.uwoisEnforced, uwoi) } - switch cst.GetEncodingType() { - case descpb.PrimaryIndexEncoding: - backingStruct := index{ - desc: cst.IndexDesc(), - validityIfConstraint: validity, - } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) - case descpb.SecondaryIndexEncoding: - if cst.IsUnique() { - backingStruct := index{ - desc: cst.IndexDesc(), - validityIfConstraint: validity, - } - allConstraints = addIfNotExists(backingStruct, allConstraints, constraintIDs) - } - default: - panic("unknown index encoding type") + } + for _, m := range mutations.uniqueWithoutIndexes { + uwoi := m.AsUniqueWithoutIndex() + if ordinal, found := byID.Get(int(uwoi.GetConstraintID())); found { + uwoisBackingStructs[ordinal].maybeMutation = uwoi.(*uniqueWithoutIndexConstraint).maybeMutation + } else { + c.all = append(c.all, uwoi) + c.uwois = append(c.uwois, uwoi) } } } - - // Populate constraintCache `c`. - for _, cst := range allConstraints { - c.all, c.allActiveAndInactive, c.allActive = - addConstraintToSetsByValidity(cst, c.all, c.allActiveAndInactive, c.allActive) - c.allChecks, c.allActiveAndInactiveChecks, c.allActiveChecks = - addConstraintToSetsByValidity(cst, c.allChecks, c.allActiveAndInactiveChecks, c.allActiveChecks) - c.allNotNulls, c.allActiveAndInactiveNotNulls, c.allActiveNotNulls = - addConstraintToSetsByValidity(cst, c.allNotNulls, c.allActiveAndInactiveNotNulls, c.allActiveNotNulls) - c.allFKs, c.allActiveAndInactiveFKs, c.allActiveFKs = - addConstraintToSetsByValidity(cst, c.allFKs, c.allActiveAndInactiveFKs, c.allActiveFKs) - c.allUniqueWithoutIndexes, c.allActiveAndInactiveUniqueWithoutIndexes, c.allActiveUniqueWithoutIndexes = - addConstraintToSetsByValidity(cst, c.allUniqueWithoutIndexes, c.allActiveAndInactiveUniqueWithoutIndexes, c.allActiveUniqueWithoutIndexes) + // Populate foreign key back-reference slice. + // These are not constraints on this table, but having them wrapped in the + // catalog.ForeignKeyConstraint interface is useful. + if numInboundFKS := len(desc.InboundFKs); numInboundFKS > 0 { + fkBackRefBackingStructs := make([]foreignKeyConstraint, numInboundFKS) + c.fkBackRefs = make([]catalog.ForeignKeyConstraint, numInboundFKS) + for i := range desc.InboundFKs { + fkBackRefBackingStructs[i].desc = &desc.InboundFKs[i] + c.fkBackRefs[i] = &fkBackRefBackingStructs[i] + } } - return &c } - -func mutationState(mutation catalog.Mutation) (ret descpb.DescriptorMutation_State) { - if mutation.DeleteOnly() { - ret = descpb.DescriptorMutation_DELETE_ONLY - } else if mutation.WriteAndDeleteOnly() { - ret = descpb.DescriptorMutation_WRITE_ONLY - } else if mutation.Backfilling() { - ret = descpb.DescriptorMutation_BACKFILLING - } else if mutation.Merging() { - ret = descpb.DescriptorMutation_MERGING - } else { - panic(errors.AssertionFailedf("unknown mutation state")) - } - return ret -} - -func mutationDirection(mutation catalog.Mutation) descpb.DescriptorMutation_Direction { - if mutation.Adding() { - return descpb.DescriptorMutation_ADD - } else { - return descpb.DescriptorMutation_DROP - } -} diff --git a/pkg/sql/catalog/tabledesc/constraint_test.go b/pkg/sql/catalog/tabledesc/constraint_test.go index 02cc52dbefee..a596cd620e7f 100644 --- a/pkg/sql/catalog/tabledesc/constraint_test.go +++ b/pkg/sql/catalog/tabledesc/constraint_test.go @@ -19,102 +19,116 @@ import ( "github.com/stretchr/testify/require" ) -// TestConstraintRetrieval tests the following three method inside catalog.TableDescriptor interface. -// - AllConstraints() []Constraint -// - AllActiveAndInactiveConstraints() []Constraint -// - AllActiveConstraints() []Constraint +// TestConstraintRetrieval tests the AllConstraints and EnforcedConstraints +// methods of the catalog.TableDescriptor interface. +// +// This test constructs a table with the following elements: +// - Primary key, index ID #1, constraint ID #1. +// - Non-unique index, index ID #2. +// - Unique index, index ID #3, constraint ID #4. +// - Check, constraint ID #2. +// - Check, constraint ID #3, unvalidated, adding. +// - Outbound foreign key, constraint ID #6. +// - Inbound foreign key, constraint ID #5. +// - Unique-without-index, constraint ID #7, dropping. +// - Unique index, index ID #4, constraint ID #8, adding. func TestConstraintRetrieval(t *testing.T) { - // Construct a table with the following constraints: - // - Primary Index: [ID_1:validated] - // - Indexes: [a non-unique index, ID_4:validated] - // - Checks: [ID_2:validated], [ID_3:unvalidated] - // - OutboundFKs: [ID_6:validated] - // - InboundFKs: [ID_5:validated] - // - UniqueWithoutIndexConstraints: [ID_7:dropping] - // - mutation slice: [ID_7:dropping:UniqueWithoutIndex, ID_8:validating:Check, ID_9:validating:UniqueIndex, a non-unique index] primaryIndex := descpb.IndexDescriptor{ + ID: 1, Unique: true, + KeyColumnIDs: []descpb.ColumnID{1}, ConstraintID: 1, EncodingType: descpb.PrimaryIndexEncoding, } - indexes := make([]descpb.IndexDescriptor, 2) - indexes[0] = descpb.IndexDescriptor{ - Unique: false, - } - indexes[1] = descpb.IndexDescriptor{ - Unique: true, - ConstraintID: 4, + indexes := []descpb.IndexDescriptor{ + { + ID: 2, + Unique: false, + KeyColumnIDs: []descpb.ColumnID{1}, + }, + { + ID: 3, + Unique: true, + ConstraintID: 4, + KeyColumnIDs: []descpb.ColumnID{1}, + }, } - checks := make([]*descpb.TableDescriptor_CheckConstraint, 2) - checks[0] = &descpb.TableDescriptor_CheckConstraint{ - Validity: descpb.ConstraintValidity_Validated, - ConstraintID: 2, - } - checks[1] = &descpb.TableDescriptor_CheckConstraint{ - Validity: descpb.ConstraintValidity_Unvalidated, - ConstraintID: 3, + checks := []*descpb.TableDescriptor_CheckConstraint{ + { + Validity: descpb.ConstraintValidity_Validated, + ConstraintID: 2, + }, + { + Validity: descpb.ConstraintValidity_Unvalidated, + ConstraintID: 3, + }, } - outboundFKs := make([]descpb.ForeignKeyConstraint, 1) - outboundFKs[0] = descpb.ForeignKeyConstraint{ - Validity: descpb.ConstraintValidity_Validated, - ConstraintID: 6, + outboundFKs := []descpb.ForeignKeyConstraint{ + { + Validity: descpb.ConstraintValidity_Validated, + ConstraintID: 6, + }, } - inboundFKs := make([]descpb.ForeignKeyConstraint, 1) - inboundFKs[0] = descpb.ForeignKeyConstraint{ - Validity: descpb.ConstraintValidity_Validated, - ConstraintID: 5, + inboundFKs := []descpb.ForeignKeyConstraint{ + { + Validity: descpb.ConstraintValidity_Validated, + ConstraintID: 5, + }, } - uniqueWithoutIndexConstraints := make([]descpb.UniqueWithoutIndexConstraint, 1) - uniqueWithoutIndexConstraints[0] = descpb.UniqueWithoutIndexConstraint{ - Validity: descpb.ConstraintValidity_Dropping, - ConstraintID: 7, + uniqueWithoutIndexConstraints := []descpb.UniqueWithoutIndexConstraint{ + { + Validity: descpb.ConstraintValidity_Dropping, + ConstraintID: 7, + ColumnIDs: []descpb.ColumnID{1}, + }, } - mutations := make([]descpb.DescriptorMutation, 4) - mutations[0] = descpb.DescriptorMutation{ - Descriptor_: &descpb.DescriptorMutation_Constraint{ - Constraint: &descpb.ConstraintToUpdate{ - ConstraintType: descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX, - Name: "unique_on_k_without_index", - UniqueWithoutIndexConstraint: uniqueWithoutIndexConstraints[0], + mutations := []descpb.DescriptorMutation{ + { + Descriptor_: &descpb.DescriptorMutation_Constraint{ + Constraint: &descpb.ConstraintToUpdate{ + ConstraintType: descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX, + UniqueWithoutIndexConstraint: uniqueWithoutIndexConstraints[0], + }, }, + State: descpb.DescriptorMutation_WRITE_ONLY, + Direction: descpb.DescriptorMutation_DROP, }, - State: descpb.DescriptorMutation_DELETE_ONLY, - Direction: descpb.DescriptorMutation_DROP, - } - mutations[1] = descpb.DescriptorMutation{ - Descriptor_: &descpb.DescriptorMutation_Constraint{ - Constraint: &descpb.ConstraintToUpdate{ - ConstraintType: descpb.ConstraintToUpdate_CHECK, - Check: descpb.TableDescriptor_CheckConstraint{ - Validity: descpb.ConstraintValidity_Validating, + { + Descriptor_: &descpb.DescriptorMutation_Constraint{ + Constraint: &descpb.ConstraintToUpdate{ + ConstraintType: descpb.ConstraintToUpdate_CHECK, + Check: *checks[1], + }}, + State: descpb.DescriptorMutation_DELETE_ONLY, + Direction: descpb.DescriptorMutation_ADD, + }, + { + Descriptor_: &descpb.DescriptorMutation_Index{ + Index: &descpb.IndexDescriptor{ + ID: 4, + Unique: true, ConstraintID: 8, - }, - }}, - State: descpb.DescriptorMutation_DELETE_ONLY, - Direction: descpb.DescriptorMutation_ADD, - } - mutations[2] = descpb.DescriptorMutation{ - Descriptor_: &descpb.DescriptorMutation_Index{ - Index: &descpb.IndexDescriptor{ - Unique: true, - ConstraintID: 9, - }}, - State: descpb.DescriptorMutation_DELETE_ONLY, - Direction: descpb.DescriptorMutation_ADD, - } - mutations[3] = descpb.DescriptorMutation{ - Descriptor_: &descpb.DescriptorMutation_Index{ - Index: &descpb.IndexDescriptor{ - Unique: false, - }}, - State: descpb.DescriptorMutation_DELETE_ONLY, - Direction: descpb.DescriptorMutation_ADD, + KeyColumnIDs: []descpb.ColumnID{1}, + }}, + State: descpb.DescriptorMutation_DELETE_ONLY, + Direction: descpb.DescriptorMutation_ADD, + }, + { + Descriptor_: &descpb.DescriptorMutation_Index{ + Index: &descpb.IndexDescriptor{ + ID: 5, + Unique: false, + KeyColumnIDs: []descpb.ColumnID{1}, + }}, + State: descpb.DescriptorMutation_DELETE_ONLY, + Direction: descpb.DescriptorMutation_ADD, + }, } tableDesc := NewBuilder(&descpb.TableDescriptor{ @@ -125,52 +139,36 @@ func TestConstraintRetrieval(t *testing.T) { InboundFKs: inboundFKs, UniqueWithoutIndexConstraints: uniqueWithoutIndexConstraints, Mutations: mutations, - }).BuildImmutable().(catalog.TableDescriptor) + }).BuildImmutableTable() t.Run("test-AllConstraints", func(t *testing.T) { all := tableDesc.AllConstraints() sort.Slice(all, func(i, j int) bool { return all[i].GetConstraintID() < all[j].GetConstraintID() }) - require.Equal(t, len(all), 9) + require.Len(t, all, 7) checkIndexBackedConstraint(t, all[0], 1, descpb.ConstraintValidity_Validated, descpb.PrimaryIndexEncoding) checkNonIndexBackedConstraint(t, all[1], 2, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_CHECK) checkNonIndexBackedConstraint(t, all[2], 3, descpb.ConstraintValidity_Unvalidated, descpb.ConstraintToUpdate_CHECK) checkIndexBackedConstraint(t, all[3], 4, descpb.ConstraintValidity_Validated, descpb.SecondaryIndexEncoding) - checkNonIndexBackedConstraint(t, all[4], 5, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) - checkNonIndexBackedConstraint(t, all[5], 6, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) - checkNonIndexBackedConstraint(t, all[6], 7, descpb.ConstraintValidity_Dropping, descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX) - checkNonIndexBackedConstraint(t, all[7], 8, descpb.ConstraintValidity_Validating, descpb.ConstraintToUpdate_CHECK) - checkIndexBackedConstraint(t, all[8], 9, descpb.ConstraintValidity_Validating, descpb.SecondaryIndexEncoding) - }) - - t.Run("test-AllActiveAndInactiveConstraints", func(t *testing.T) { - allActiveAndInactive := tableDesc.AllActiveAndInactiveConstraints() - sort.Slice(allActiveAndInactive, func(i, j int) bool { - return allActiveAndInactive[i].GetConstraintID() < allActiveAndInactive[j].GetConstraintID() - }) - require.Equal(t, len(allActiveAndInactive), 8) - checkIndexBackedConstraint(t, allActiveAndInactive[0], 1, descpb.ConstraintValidity_Validated, descpb.PrimaryIndexEncoding) - checkNonIndexBackedConstraint(t, allActiveAndInactive[1], 2, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_CHECK) - checkNonIndexBackedConstraint(t, allActiveAndInactive[2], 3, descpb.ConstraintValidity_Unvalidated, descpb.ConstraintToUpdate_CHECK) - checkIndexBackedConstraint(t, allActiveAndInactive[3], 4, descpb.ConstraintValidity_Validated, descpb.SecondaryIndexEncoding) - checkNonIndexBackedConstraint(t, allActiveAndInactive[4], 5, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) - checkNonIndexBackedConstraint(t, allActiveAndInactive[5], 6, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) - checkNonIndexBackedConstraint(t, allActiveAndInactive[6], 8, descpb.ConstraintValidity_Validating, descpb.ConstraintToUpdate_CHECK) - checkIndexBackedConstraint(t, allActiveAndInactive[7], 9, descpb.ConstraintValidity_Validating, descpb.SecondaryIndexEncoding) + // ID 5 is missing: inbound foreign keys are not constraints. + checkNonIndexBackedConstraint(t, all[4], 6, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) + checkNonIndexBackedConstraint(t, all[5], 7, descpb.ConstraintValidity_Dropping, descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX) + checkIndexBackedConstraint(t, all[6], 8, descpb.ConstraintValidity_Validating, descpb.SecondaryIndexEncoding) }) - - t.Run("test-AllActiveConstraints", func(t *testing.T) { - allActive := tableDesc.AllActiveConstraints() - sort.Slice(allActive, func(i, j int) bool { - return allActive[i].GetConstraintID() < allActive[j].GetConstraintID() + t.Run("test-EnforcedConstraints", func(t *testing.T) { + enforced := tableDesc.EnforcedConstraints() + sort.Slice(enforced, func(i, j int) bool { + return enforced[i].GetConstraintID() < enforced[j].GetConstraintID() }) - require.Equal(t, len(allActive), 5) - checkIndexBackedConstraint(t, allActive[0], 1, descpb.ConstraintValidity_Validated, descpb.PrimaryIndexEncoding) - checkNonIndexBackedConstraint(t, allActive[1], 2, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_CHECK) - checkIndexBackedConstraint(t, allActive[2], 4, descpb.ConstraintValidity_Validated, descpb.SecondaryIndexEncoding) - checkNonIndexBackedConstraint(t, allActive[3], 5, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) - checkNonIndexBackedConstraint(t, allActive[4], 6, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) + require.Len(t, enforced, 6) + checkIndexBackedConstraint(t, enforced[0], 1, descpb.ConstraintValidity_Validated, descpb.PrimaryIndexEncoding) + checkNonIndexBackedConstraint(t, enforced[1], 2, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_CHECK) + checkNonIndexBackedConstraint(t, enforced[2], 3, descpb.ConstraintValidity_Unvalidated, descpb.ConstraintToUpdate_CHECK) + checkIndexBackedConstraint(t, enforced[3], 4, descpb.ConstraintValidity_Validated, descpb.SecondaryIndexEncoding) + // ID 5 is missing: inbound foreign keys are not constraints. + checkNonIndexBackedConstraint(t, enforced[4], 6, descpb.ConstraintValidity_Validated, descpb.ConstraintToUpdate_FOREIGN_KEY) + checkNonIndexBackedConstraint(t, enforced[5], 7, descpb.ConstraintValidity_Dropping, descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX) }) } @@ -188,10 +186,7 @@ func checkIndexBackedConstraint( ) { require.Equal(t, expectedID, c.GetConstraintID()) require.Equal(t, expectedValidity, c.GetConstraintValidity()) - idx := c.AsUnique() - if idx != nil { - idx = c.AsPrimaryKey() - } + idx := c.AsUniqueWithIndex() require.NotNil(t, idx) require.Equal(t, expectedEncodingType, idx.IndexDesc().EncodingType) if expectedEncodingType == descpb.SecondaryIndexEncoding { @@ -213,22 +208,20 @@ func checkNonIndexBackedConstraint( switch expectedType { case descpb.ConstraintToUpdate_CHECK: require.NotNil(t, c.AsCheck()) - require.Zero(t, c.NotNullColumnID()) + require.False(t, c.AsCheck().IsNotNullColumnConstraint()) require.Nil(t, c.AsForeignKey()) require.Nil(t, c.AsUniqueWithoutIndex()) case descpb.ConstraintToUpdate_NOT_NULL: require.NotNil(t, c.AsCheck()) - require.NotZero(t, c.NotNullColumnID()) + require.True(t, c.AsCheck().IsNotNullColumnConstraint()) require.Nil(t, c.AsForeignKey()) require.Nil(t, c.AsUniqueWithoutIndex()) case descpb.ConstraintToUpdate_FOREIGN_KEY: require.Nil(t, c.AsCheck()) - require.Zero(t, c.NotNullColumnID()) require.NotNil(t, c.AsForeignKey()) require.Nil(t, c.AsUniqueWithoutIndex()) case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: require.Nil(t, c.AsCheck()) - require.Zero(t, c.NotNullColumnID()) require.Nil(t, c.AsForeignKey()) require.NotNil(t, c.AsUniqueWithoutIndex()) default: diff --git a/pkg/sql/catalog/tabledesc/helpers_test.go b/pkg/sql/catalog/tabledesc/helpers_test.go index f635e8f76d0a..407d7825a114 100644 --- a/pkg/sql/catalog/tabledesc/helpers_test.go +++ b/pkg/sql/catalog/tabledesc/helpers_test.go @@ -46,7 +46,7 @@ func ValidateConstraints(immI catalog.TableDescriptor) error { return errors.Errorf("expected immutable descriptor") } cea := &constraintValidationErrorAccumulator{} - imm.validateConstraintIDs(cea) + imm.validateConstraintNamesAndIDs(cea) if cea.Errors == nil { return nil } diff --git a/pkg/sql/catalog/tabledesc/index.go b/pkg/sql/catalog/tabledesc/index.go index 08ac17bccb7a..f021cfd8f4a8 100644 --- a/pkg/sql/catalog/tabledesc/index.go +++ b/pkg/sql/catalog/tabledesc/index.go @@ -27,7 +27,7 @@ import ( ) var _ catalog.Index = (*index)(nil) -var _ catalog.Constraint = (*index)(nil) +var _ catalog.UniqueWithIndexConstraint = (*index)(nil) // index implements the catalog.Index interface by wrapping the protobuf index // descriptor along with some metadata from its parent table descriptor. @@ -36,9 +36,8 @@ var _ catalog.Constraint = (*index)(nil) // (i.e. PRIMARY KEY or UNIQUE). type index struct { maybeMutation - desc *descpb.IndexDescriptor - ordinal int - validityIfConstraint descpb.ConstraintValidity // validity of this index-backed-constraint if is. + desc *descpb.IndexDescriptor + ordinal int } // IndexDesc returns the underlying protobuf descriptor. @@ -159,24 +158,18 @@ func (w index) ExplicitColumnStartIdx() int { return w.desc.ExplicitColumnStartIdx() } -// IsValidOriginIndex returns whether the index can serve as an origin index for -// a foreign key constraint with the provided set of originColIDs. -func (w index) IsValidOriginIndex(originColIDs descpb.ColumnIDs) bool { - return w.desc.IsValidOriginIndex(originColIDs) -} - -// IsHelpfulOriginIndex returns whether the index may be a helpful index for -// performing foreign key checks and cascades for a foreign key with the given -// origin columns. -func (w index) IsHelpfulOriginIndex(originColIDs descpb.ColumnIDs) bool { - return w.desc.IsHelpfulOriginIndex(originColIDs) +// IsValidOriginIndex implements the catalog.Index interface. +func (w index) IsValidOriginIndex(fk catalog.ForeignKeyConstraint) bool { + if w.IsPartial() { + return false + } + return descpb.ColumnIDs(w.desc.KeyColumnIDs).HasPrefix(fk.ForeignKeyDesc().OriginColumnIDs) } -// IsValidReferencedUniqueConstraint returns whether the index can serve as a -// referenced index for a foreign key constraint with the provided set of -// referencedColumnIDs. -func (w index) IsValidReferencedUniqueConstraint(referencedColIDs descpb.ColumnIDs) bool { - return w.desc.IsValidReferencedUniqueConstraint(referencedColIDs) +// IsValidReferencedUniqueConstraint implements the catalog.UniqueConstraint +// interface. +func (w index) IsValidReferencedUniqueConstraint(fk catalog.ForeignKeyConstraint) bool { + return w.desc.IsValidReferencedUniqueConstraint(fk.ForeignKeyDesc().ReferencedColumnIDs) } // HasOldStoredColumns returns whether the index has stored columns in the old @@ -415,7 +408,7 @@ func (w index) CreatedAt() time.Time { return timeutil.Unix(0, w.desc.CreatedAtNanos) } -// IsTemporaryIndexForBackfill() returns true iff the index is +// IsTemporaryIndexForBackfill returns true iff the index is // an index being used as the temporary index being used by an // in-progress index backfill. // @@ -425,40 +418,30 @@ func (w index) IsTemporaryIndexForBackfill() bool { return w.desc.UseDeletePreservingEncoding } -// NotNullColumnID implements the catalog.Constraint interface. -func (w index) NotNullColumnID() descpb.ColumnID { - return 0 -} - -// AsCheck implements the catalog.Constraint interface. -func (w index) AsCheck() *descpb.TableDescriptor_CheckConstraint { +// AsCheck implements the catalog.ConstraintProvider interface. +func (w index) AsCheck() catalog.CheckConstraint { return nil } -// AsForeignKey implements the catalog.Constraint interface. -func (w index) AsForeignKey() *descpb.ForeignKeyConstraint { +// AsForeignKey implements the catalog.ConstraintProvider interface. +func (w index) AsForeignKey() catalog.ForeignKeyConstraint { return nil } -// AsUniqueWithoutIndex implements the catalog.Constraint interface. -func (w index) AsUniqueWithoutIndex() *descpb.UniqueWithoutIndexConstraint { +// AsUniqueWithoutIndex implements the catalog.ConstraintProvider interface. +func (w index) AsUniqueWithoutIndex() catalog.UniqueWithoutIndexConstraint { return nil } -// AsPrimaryKey implements the catalog.Constraint interface. -func (w index) AsPrimaryKey() catalog.Index { - if w.GetEncodingType() != descpb.PrimaryIndexEncoding { - return nil +// AsUniqueWithIndex implements the catalog.ConstraintProvider interface. +func (w index) AsUniqueWithIndex() catalog.UniqueWithIndexConstraint { + if w.Primary() { + return &w } - return w -} - -// AsUnique implements the catalog.Constraint interface. -func (w index) AsUnique() catalog.Index { - if !w.desc.Unique { - return nil + if w.IsUnique() && !w.desc.UseDeletePreservingEncoding { + return &w } - return w + return nil } // String implements the catalog.Constraint interface. @@ -466,9 +449,30 @@ func (w index) String() string { return fmt.Sprintf("%v", w.desc) } -// GetConstraintValidity implements catalog.Constraint interface. +// IsConstraintValidated implements the catalog.Constraint interface. +func (w index) IsConstraintValidated() bool { + return !w.IsMutation() +} + +// IsConstraintUnvalidated implements the catalog.Constraint interface. +func (w index) IsConstraintUnvalidated() bool { + return false +} + +// GetConstraintValidity implements the catalog.Constraint interface. func (w index) GetConstraintValidity() descpb.ConstraintValidity { - return w.validityIfConstraint + if w.Adding() { + return descpb.ConstraintValidity_Validating + } + if w.Dropped() { + return descpb.ConstraintValidity_Dropping + } + return descpb.ConstraintValidity_Validated +} + +// IsEnforced implements the catalog.Constraint interface. +func (w index) IsEnforced() bool { + return !w.IsMutation() || w.WriteAndDeleteOnly() } // partitioning is the backing struct for a catalog.Partitioning interface. @@ -589,7 +593,7 @@ func (p partitioning) NumImplicitColumns() int { // indexCache contains precomputed slices of catalog.Index interfaces. type indexCache struct { - primary catalog.Index + primary catalog.UniqueWithIndexConstraint all []catalog.Index active []catalog.Index nonDrop []catalog.Index @@ -623,7 +627,7 @@ func newIndexCache(desc *descpb.TableDescriptor, mutations *mutationCache) *inde c.all = append(c.all, m.AsIndex()) } // Populate the remaining fields in c. - c.primary = c.all[0] + c.primary = c.all[0].AsUniqueWithIndex() c.active = c.all[:numPublic] c.publicNonPrimary = c.active[1:] for _, idx := range c.all[1:] { diff --git a/pkg/sql/catalog/tabledesc/mutation.go b/pkg/sql/catalog/tabledesc/mutation.go index 9ff55ac9c419..0da565ca9d8e 100644 --- a/pkg/sql/catalog/tabledesc/mutation.go +++ b/pkg/sql/catalog/tabledesc/mutation.go @@ -11,18 +11,17 @@ package tabledesc import ( - "fmt" - "github.com/cockroachdb/cockroach/pkg/sql/catalog" "github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/util/hlc" "github.com/cockroachdb/cockroach/pkg/util/iterutil" - "github.com/cockroachdb/errors" ) var _ catalog.TableElementMaybeMutation = maybeMutation{} -var _ catalog.TableElementMaybeMutation = constraint{} +var _ catalog.TableElementMaybeMutation = checkConstraint{} +var _ catalog.TableElementMaybeMutation = foreignKeyConstraint{} +var _ catalog.TableElementMaybeMutation = uniqueWithoutIndexConstraint{} var _ catalog.TableElementMaybeMutation = primaryKeySwap{} var _ catalog.TableElementMaybeMutation = computedColumnSwap{} var _ catalog.TableElementMaybeMutation = materializedViewRefresh{} @@ -88,93 +87,6 @@ func (mm maybeMutation) Dropped() bool { return mm.mutationDirection == descpb.DescriptorMutation_DROP } -// constraint implements the catalog.Constraint interface by wrapping -// the protobuf descriptor (*descpb.ConstraintToUpdate) along with -// some metadata if this constraint is a mutation. -// N.B. This struct is intended for non-index-backed-constraints. -type constraint struct { - maybeMutation - desc *descpb.ConstraintToUpdate -} - -// GetName implements the catalog.Constraint interface. -func (c constraint) GetName() string { - return c.desc.Name -} - -// NotNullColumnID implements the catalog.Constraint interface. -func (c constraint) NotNullColumnID() descpb.ColumnID { - return c.desc.NotNullColumn -} - -// AsCheck implements the catalog.Constraint interface. -func (c constraint) AsCheck() *descpb.TableDescriptor_CheckConstraint { - switch c.desc.ConstraintType { - case descpb.ConstraintToUpdate_CHECK, descpb.ConstraintToUpdate_NOT_NULL: - return &c.desc.Check - } - return nil -} - -// AsForeignKey implements the catalog.Constraint interface. -func (c constraint) AsForeignKey() *descpb.ForeignKeyConstraint { - switch c.desc.ConstraintType { - case descpb.ConstraintToUpdate_FOREIGN_KEY: - return &c.desc.ForeignKey - } - return nil -} - -// AsUniqueWithoutIndex implements the catalog.Constraint interface. -func (c constraint) AsUniqueWithoutIndex() *descpb.UniqueWithoutIndexConstraint { - switch c.desc.ConstraintType { - case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: - return &c.desc.UniqueWithoutIndexConstraint - } - return nil -} - -// AsPrimaryKey implements the catalog.Constraint interface. -func (c constraint) AsPrimaryKey() catalog.Index { - return nil -} - -// AsUnique implements the catalog.Constraint interface. -func (c constraint) AsUnique() catalog.Index { - return nil -} - -// String implements the catalog.Constraint interface. -func (c constraint) String() string { - return fmt.Sprintf("%v", c.desc) -} - -// GetConstraintID implements the catalog.Constraint interface. -func (c constraint) GetConstraintID() descpb.ConstraintID { - switch c.desc.ConstraintType { - case descpb.ConstraintToUpdate_CHECK, descpb.ConstraintToUpdate_NOT_NULL: - return c.desc.Check.ConstraintID - case descpb.ConstraintToUpdate_FOREIGN_KEY: - return c.desc.ForeignKey.ConstraintID - case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: - return c.desc.UniqueWithoutIndexConstraint.ConstraintID - } - panic(errors.AssertionFailedf("unknown constraint type %v", c.desc.ConstraintType)) -} - -// GetConstraintValidity implements the catalog.Constraint interface. -func (c constraint) GetConstraintValidity() descpb.ConstraintValidity { - switch c.desc.ConstraintType { - case descpb.ConstraintToUpdate_CHECK, descpb.ConstraintToUpdate_NOT_NULL: - return c.desc.Check.Validity - case descpb.ConstraintToUpdate_FOREIGN_KEY: - return c.desc.ForeignKey.Validity - case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: - return c.desc.UniqueWithoutIndexConstraint.Validity - } - panic(errors.AssertionFailedf("unknown constraint type %v", c.desc.ConstraintType)) -} - // modifyRowLevelTTL implements the catalog.ModifyRowLevelTTL interface. type modifyRowLevelTTL struct { maybeMutation @@ -309,14 +221,16 @@ func (c materializedViewRefresh) TableWithNewIndexes( // mutation implements the type mutation struct { maybeMutation - column catalog.Column - index catalog.Index - constraint catalog.Constraint - pkSwap catalog.PrimaryKeySwap - ccSwap catalog.ComputedColumnSwap - mvRefresh catalog.MaterializedViewRefresh - modifyRowLevelTTL catalog.ModifyRowLevelTTL - mutationOrdinal int + column catalog.Column + index catalog.Index + check catalog.CheckConstraint + foreignKey catalog.ForeignKeyConstraint + uniqueWithoutIndex catalog.UniqueWithoutIndexConstraint + pkSwap catalog.PrimaryKeySwap + ccSwap catalog.ComputedColumnSwap + mvRefresh catalog.MaterializedViewRefresh + modifyRowLevelTTL catalog.ModifyRowLevelTTL + mutationOrdinal int } // AsColumn returns the corresponding Column if the mutation is on a column, @@ -331,10 +245,38 @@ func (m mutation) AsIndex() catalog.Index { return m.index } -// AsConstraint returns the corresponding Constraint if the -// mutation is on a constraint, nil otherwise. -func (m mutation) AsConstraint() catalog.Constraint { - return m.constraint +// AsConstraintWithoutIndex implements the catalog.Mutation interface. +func (m mutation) AsConstraintWithoutIndex() catalog.WithoutIndexConstraint { + if m.check != nil { + return m.check + } + if m.foreignKey != nil { + return m.foreignKey + } + return m.uniqueWithoutIndex +} + +// AsCheck implements the catalog.ConstraintProvider interface. +func (m mutation) AsCheck() catalog.CheckConstraint { + return m.check +} + +// AsForeignKey implements the catalog.ConstraintProvider interface. +func (m mutation) AsForeignKey() catalog.ForeignKeyConstraint { + return m.foreignKey +} + +// AsUniqueWithoutIndex implements the catalog.ConstraintProvider interface. +func (m mutation) AsUniqueWithoutIndex() catalog.UniqueWithoutIndexConstraint { + return m.uniqueWithoutIndex +} + +// AsUniqueWithIndex implements the catalog.ConstraintProvider interface. +func (m mutation) AsUniqueWithIndex() catalog.UniqueWithIndexConstraint { + if m.index == nil { + return nil + } + return m.index.AsUniqueWithIndex() } // AsPrimaryKeySwap returns the corresponding PrimaryKeySwap if the mutation @@ -369,9 +311,10 @@ func (m mutation) MutationOrdinal() int { // mutationCache contains precomputed slices of catalog.Mutation interfaces. type mutationCache struct { - all []catalog.Mutation - columns []catalog.Mutation - indexes []catalog.Mutation + all []catalog.Mutation + columns []catalog.Mutation + indexes []catalog.Mutation + checks, fks, uniqueWithoutIndexes []catalog.Mutation } // newMutationCache returns a fresh fully-populated mutationCache struct for the @@ -386,7 +329,9 @@ func newMutationCache(desc *descpb.TableDescriptor) *mutationCache { backingStructs := make([]mutation, len(desc.Mutations)) var columns []column var indexes []index - var constraints []constraint + var checks []checkConstraint + var fks []foreignKeyConstraint + var uniqueWithoutIndexes []uniqueWithoutIndexConstraint var pkSwaps []primaryKeySwap var ccSwaps []computedColumnSwap var mvRefreshes []materializedViewRefresh @@ -417,11 +362,27 @@ func newMutationCache(desc *descpb.TableDescriptor) *mutationCache { }) backingStructs[i].index = &indexes[len(indexes)-1] } else if pb := m.GetConstraint(); pb != nil { - constraints = append(constraints, constraint{ - maybeMutation: mm, - desc: pb, - }) - backingStructs[i].constraint = &constraints[len(constraints)-1] + switch pb.ConstraintType { + case descpb.ConstraintToUpdate_CHECK, descpb.ConstraintToUpdate_NOT_NULL: + checks = append(checks, checkConstraint{ + constraintBase: constraintBase{maybeMutation: mm}, + desc: &pb.Check, + }) + backingStructs[i].check = &checks[len(checks)-1] + case descpb.ConstraintToUpdate_FOREIGN_KEY: + fks = append(fks, foreignKeyConstraint{ + constraintBase: constraintBase{maybeMutation: mm}, + desc: &pb.ForeignKey, + }) + backingStructs[i].foreignKey = &fks[len(fks)-1] + case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: + uniqueWithoutIndexes = append(uniqueWithoutIndexes, uniqueWithoutIndexConstraint{ + constraintBase: constraintBase{maybeMutation: mm}, + desc: &pb.UniqueWithoutIndexConstraint, + }) + backingStructs[i].uniqueWithoutIndex = &uniqueWithoutIndexes[len(uniqueWithoutIndexes)-1] + + } } else if pb := m.GetPrimaryKeySwap(); pb != nil { pkSwaps = append(pkSwaps, primaryKeySwap{ maybeMutation: mm, @@ -461,11 +422,26 @@ func newMutationCache(desc *descpb.TableDescriptor) *mutationCache { if len(indexes) > 0 { c.indexes = make([]catalog.Mutation, 0, len(indexes)) } + if len(checks) > 0 { + c.checks = make([]catalog.Mutation, 0, len(checks)) + } + if len(fks) > 0 { + c.fks = make([]catalog.Mutation, 0, len(fks)) + } + if len(uniqueWithoutIndexes) > 0 { + c.uniqueWithoutIndexes = make([]catalog.Mutation, 0, len(uniqueWithoutIndexes)) + } for _, m := range c.all { if col := m.AsColumn(); col != nil { c.columns = append(c.columns, m) } else if idx := m.AsIndex(); idx != nil { c.indexes = append(c.indexes, m) + } else if ck := m.AsCheck(); ck != nil { + c.checks = append(c.checks, m) + } else if fk := m.AsForeignKey(); fk != nil { + c.fks = append(c.fks, m) + } else if uwoi := m.AsUniqueWithoutIndex(); uwoi != nil { + c.uniqueWithoutIndexes = append(c.uniqueWithoutIndexes, m) } } return &c diff --git a/pkg/sql/catalog/tabledesc/safe_format_test.go b/pkg/sql/catalog/tabledesc/safe_format_test.go index a7d1b0b9dc22..db767b9f91c3 100644 --- a/pkg/sql/catalog/tabledesc/safe_format_test.go +++ b/pkg/sql/catalog/tabledesc/safe_format_test.go @@ -237,6 +237,7 @@ func TestSafeMessage(t *testing.T) { mutable.PrimaryIndex.StoreColumnNames = append(mutable.PrimaryIndex.StoreColumnNames, "c") mutable.NextColumnID = 6 mutable.NextIndexID = 4 + mutable.NextConstraintID = 8 mutable.Families[0].ColumnNames = append(mutable.Families[0].ColumnNames, "c") mutable.Families[0].ColumnIDs = append(mutable.Families[0].ColumnIDs, 5) mutable.ModificationTime = hlc.Timestamp{WallTime: 1e9} diff --git a/pkg/sql/catalog/tabledesc/structured.go b/pkg/sql/catalog/tabledesc/structured.go index 715a1886c5a8..809b59d32823 100644 --- a/pkg/sql/catalog/tabledesc/structured.go +++ b/pkg/sql/catalog/tabledesc/structured.go @@ -21,7 +21,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/docs" "github.com/cockroachdb/cockroach/pkg/keys" "github.com/cockroachdb/cockroach/pkg/roachpb" - "github.com/cockroachdb/cockroach/pkg/settings/cluster" "github.com/cockroachdb/cockroach/pkg/sql/catalog" "github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo" @@ -197,97 +196,6 @@ func BuildIndexName(tableDesc *Mutable, idx *descpb.IndexDescriptor) (string, er return name, nil } -// AllActiveAndInactiveChecks implements the TableDescriptor interface. -func (desc *wrapper) AllActiveAndInactiveChecks() []*descpb.TableDescriptor_CheckConstraint { - // A check constraint could be both on the table descriptor and in the - // list of mutations while the constraint is validated for existing rows. In - // that case, the constraint is in the Validating state, and we avoid - // including it twice. (Note that even though unvalidated check constraints - // cannot be added as of 19.1, they can still exist if they were created under - // previous versions.) - checks := make([]*descpb.TableDescriptor_CheckConstraint, 0, len(desc.Checks)) - for _, c := range desc.Checks { - // While a constraint is being validated for existing rows or being dropped, - // the constraint is present both on the table descriptor and in the - // mutations list in the Validating or Dropping state, so those constraints - // are excluded here to avoid double-counting. - if c.Validity != descpb.ConstraintValidity_Validating && - c.Validity != descpb.ConstraintValidity_Dropping { - checks = append(checks, c) - } - } - for _, m := range desc.Mutations { - if c := m.GetConstraint(); c != nil && c.ConstraintType == descpb.ConstraintToUpdate_CHECK { - // Any mutations that are dropped should be - // excluded to avoid returning duplicates. - if m.Direction != descpb.DescriptorMutation_DROP { - checks = append(checks, &c.Check) - } - } - } - return checks -} - -// GetColumnFamilyForShard returns the column family that a newly added shard column -// should be assigned to, given the set of columns it's computed from. -// -// This is currently the column family of the first column in the set of index columns. -func GetColumnFamilyForShard(desc *Mutable, idxColumns []string) string { - for _, f := range desc.Families { - for _, fCol := range f.ColumnNames { - if fCol == idxColumns[0] { - return f.Name - } - } - } - return "" -} - -// AllActiveAndInactiveUniqueWithoutIndexConstraints implements the -// TableDescriptor interface. -func (desc *wrapper) AllActiveAndInactiveUniqueWithoutIndexConstraints() []*descpb.UniqueWithoutIndexConstraint { - ucs := make([]*descpb.UniqueWithoutIndexConstraint, 0, len(desc.UniqueWithoutIndexConstraints)) - for i := range desc.UniqueWithoutIndexConstraints { - uc := &desc.UniqueWithoutIndexConstraints[i] - // While a constraint is being validated for existing rows or being dropped, - // the constraint is present both on the table descriptor and in the - // mutations list in the Validating or Dropping state, so those constraints - // are excluded here to avoid double-counting. - if uc.Validity != descpb.ConstraintValidity_Validating && - uc.Validity != descpb.ConstraintValidity_Dropping { - ucs = append(ucs, uc) - } - } - for i := range desc.Mutations { - if c := desc.Mutations[i].GetConstraint(); c != nil && - c.ConstraintType == descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX { - ucs = append(ucs, &c.UniqueWithoutIndexConstraint) - } - } - return ucs -} - -// AllActiveAndInactiveForeignKeys implements the TableDescriptor interface. -func (desc *wrapper) AllActiveAndInactiveForeignKeys() []*descpb.ForeignKeyConstraint { - fks := make([]*descpb.ForeignKeyConstraint, 0, len(desc.OutboundFKs)) - for i := range desc.OutboundFKs { - fk := &desc.OutboundFKs[i] - // While a constraint is being validated for existing rows or being dropped, - // the constraint is present both on the table descriptor and in the - // mutations list in the Validating or Dropping state, so those constraints - // are excluded here to avoid double-counting. - if fk.Validity != descpb.ConstraintValidity_Validating && fk.Validity != descpb.ConstraintValidity_Dropping { - fks = append(fks, fk) - } - } - for i := range desc.Mutations { - if c := desc.Mutations[i].GetConstraint(); c != nil && c.ConstraintType == descpb.ConstraintToUpdate_FOREIGN_KEY { - fks = append(fks, &c.ForeignKey) - } - } - return fks -} - // ForeachDependedOnBy implements the TableDescriptor interface. func (desc *wrapper) ForeachDependedOnBy( f func(dep *descpb.TableDescriptor_Reference) error, @@ -300,29 +208,6 @@ func (desc *wrapper) ForeachDependedOnBy( return nil } -// ForeachOutboundFK implements the TableDescriptor interface. -func (desc *wrapper) ForeachOutboundFK( - f func(constraint *descpb.ForeignKeyConstraint) error, -) error { - for i := range desc.OutboundFKs { - if err := f(&desc.OutboundFKs[i]); err != nil { - return iterutil.Map(err) - } - } - return nil -} - -// ForeachInboundFK calls f for every inbound foreign key in desc until an -// error is returned. -func (desc *wrapper) ForeachInboundFK(f func(fk *descpb.ForeignKeyConstraint) error) error { - for i := range desc.InboundFKs { - if err := f(&desc.InboundFKs[i]); err != nil { - return iterutil.Map(err) - } - } - return nil -} - // NumFamilies implements the TableDescriptor interface. func (desc *wrapper) NumFamilies() int { return len(desc.Families) @@ -1341,50 +1226,37 @@ func (desc *wrapper) NamesForColumnIDs(ids descpb.ColumnIDs) ([]string, error) { // DropConstraint drops a constraint, either by removing it from the table // descriptor or by queuing a mutation for a schema change. func (desc *Mutable) DropConstraint( - ctx context.Context, - name string, - detail descpb.ConstraintDetail, - removeFK func(*Mutable, *descpb.ForeignKeyConstraint) error, - settings *cluster.Settings, + constraint catalog.Constraint, removeFKBackRef func(catalog.ForeignKeyConstraint) error, ) error { - switch detail.Kind { - case descpb.ConstraintTypePK: - { - primaryIndex := desc.PrimaryIndex + if u := constraint.AsUniqueWithIndex(); u != nil { + if u.Primary() { + primaryIndex := u.IndexDescDeepCopy() primaryIndex.Disabled = true desc.SetPrimaryIndex(primaryIndex) + return nil } - return nil + return unimplemented.NewWithIssueDetailf(42840, "drop-constraint-unique", + "cannot drop UNIQUE constraint %q using ALTER TABLE DROP CONSTRAINT, use DROP INDEX CASCADE instead", + tree.ErrNameString(u.GetName())) + } + if constraint.Adding() { + return unimplemented.NewWithIssuef(42844, + "constraint %q in the middle of being added, try again later", constraint.GetName()) + } + if constraint.Dropped() { + return unimplemented.NewWithIssuef(42844, + "constraint %q in the middle of being dropped", constraint.GetName()) + } - case descpb.ConstraintTypeUnique: - if detail.Index != nil { - return unimplemented.NewWithIssueDetailf(42840, "drop-constraint-unique", - "cannot drop UNIQUE constraint %q using ALTER TABLE DROP CONSTRAINT, use DROP INDEX CASCADE instead", - tree.ErrNameStringP(&detail.Index.Name)) - } - if detail.UniqueWithoutIndexConstraint == nil { - return errors.AssertionFailedf( - "Index or UniqueWithoutIndexConstraint must be non-nil for a unique constraint", - ) - } - if detail.UniqueWithoutIndexConstraint.Validity == descpb.ConstraintValidity_Validating { - return unimplemented.NewWithIssueDetailf(42844, - "drop-constraint-unique-validating", - "constraint %q in the middle of being added, try again later", name) - } - if detail.UniqueWithoutIndexConstraint.Validity == descpb.ConstraintValidity_Dropping { - return unimplemented.NewWithIssueDetailf(42844, - "drop-constraint-unique-mutation", - "constraint %q in the middle of being dropped", name) - } + if uwoi := constraint.AsUniqueWithoutIndex(); uwoi != nil { // Search through the descriptor's unique constraints and delete the // one that we're supposed to be deleting. for i := range desc.UniqueWithoutIndexConstraints { ref := &desc.UniqueWithoutIndexConstraints[i] - if ref.Name == name { + if ref.Name == uwoi.GetName() { // If the constraint is unvalidated, there's no assumption that it must // hold for all rows, so it can be dropped immediately. - if detail.UniqueWithoutIndexConstraint.Validity == descpb.ConstraintValidity_Unvalidated { + if uwoi.IsConstraintUnvalidated() { desc.UniqueWithoutIndexConstraints = append( desc.UniqueWithoutIndexConstraints[:i], desc.UniqueWithoutIndexConstraints[i+1:]..., ) @@ -1395,24 +1267,15 @@ func (desc *Mutable) DropConstraint( return nil } } - - case descpb.ConstraintTypeCheck: - if detail.CheckConstraint.Validity == descpb.ConstraintValidity_Validating { - return unimplemented.NewWithIssueDetailf(42844, "drop-constraint-check-mutation", - "constraint %q in the middle of being added, try again later", name) - } - if detail.CheckConstraint.Validity == descpb.ConstraintValidity_Dropping { - return unimplemented.NewWithIssueDetailf(42844, "drop-constraint-check-mutation", - "constraint %q in the middle of being dropped", name) - } + } else if ck := constraint.AsCheck(); ck != nil { for i, c := range desc.Checks { - if c.Name == name { + if c.Name == ck.GetName() { // If the constraint is unvalidated, there's no assumption that it must // hold for all rows, so it can be dropped immediately. // We also drop the constraint immediately instead of queuing a mutation // unless the cluster is fully upgraded to 19.2, for backward // compatibility. - if detail.CheckConstraint.Validity == descpb.ConstraintValidity_Unvalidated { + if ck.IsConstraintUnvalidated() { desc.Checks = append(desc.Checks[:i], desc.Checks[i+1:]...) return nil } @@ -1421,28 +1284,17 @@ func (desc *Mutable) DropConstraint( return nil } } - - case descpb.ConstraintTypeFK: - if detail.FK.Validity == descpb.ConstraintValidity_Validating { - return unimplemented.NewWithIssueDetailf(42844, - "drop-constraint-fk-validating", - "constraint %q in the middle of being added, try again later", name) - } - if detail.FK.Validity == descpb.ConstraintValidity_Dropping { - return unimplemented.NewWithIssueDetailf(42844, - "drop-constraint-fk-mutation", - "constraint %q in the middle of being dropped", name) - } + } else if fk := constraint.AsForeignKey(); fk != nil { // Search through the descriptor's foreign key constraints and delete the // one that we're supposed to be deleting. for i := range desc.OutboundFKs { ref := &desc.OutboundFKs[i] - if ref.Name == name { + if ref.Name == fk.GetName() { // If the constraint is unvalidated, there's no assumption that it must // hold for all rows, so it can be dropped immediately. - if detail.FK.Validity == descpb.ConstraintValidity_Unvalidated { + if fk.IsConstraintUnvalidated() { // Remove the backreference. - if err := removeFK(desc, detail.FK); err != nil { + if err := removeFKBackRef(fk); err != nil { return err } desc.OutboundFKs = append(desc.OutboundFKs[:i], desc.OutboundFKs[i+1:]...) @@ -1453,114 +1305,49 @@ func (desc *Mutable) DropConstraint( return nil } } - - default: - return unimplemented.Newf(fmt.Sprintf("drop-constraint-%s", detail.Kind), - "constraint %q has unsupported type", tree.ErrNameString(name)) - } - - // Check if the constraint can be found in a mutation, complain appropriately. - for i := range desc.Mutations { - m := &desc.Mutations[i] - if m.GetConstraint() != nil && m.GetConstraint().Name == name { - switch m.Direction { - case descpb.DescriptorMutation_ADD: - return unimplemented.NewWithIssueDetailf(42844, - "drop-constraint-mutation", - "constraint %q in the middle of being added, try again later", name) - case descpb.DescriptorMutation_DROP: - return unimplemented.NewWithIssueDetailf(42844, - "drop-constraint-mutation", - "constraint %q in the middle of being dropped", name) - } - } } - return errors.AssertionFailedf("constraint %q not found on table %q", name, desc.Name) + return errors.AssertionFailedf("constraint %q not found on table %q", constraint.GetName(), desc.Name) } // RenameConstraint renames a constraint. func (desc *Mutable) RenameConstraint( - detail descpb.ConstraintDetail, - oldName, newName string, + constraint catalog.Constraint, + newName string, dependentViewRenameError func(string, descpb.ID) error, - renameFK func(*Mutable, *descpb.ForeignKeyConstraint, string) error, + renameFK func(*Mutable, catalog.ForeignKeyConstraint, string) error, ) error { - switch detail.Kind { - case descpb.ConstraintTypePK: + if u := constraint.AsUniqueWithIndex(); u != nil { for _, tableRef := range desc.DependedOnBy { - if tableRef.IndexID != detail.Index.ID { + if tableRef.IndexID != u.GetID() { continue } return dependentViewRenameError("index", tableRef.ID) } - idx, err := desc.FindIndexWithID(detail.Index.ID) - if err != nil { - return err - } - idx.IndexDesc().Name = newName - return nil - - case descpb.ConstraintTypeUnique: - if detail.Index != nil { - for _, tableRef := range desc.DependedOnBy { - if tableRef.IndexID != detail.Index.ID { - continue - } - return dependentViewRenameError("index", tableRef.ID) - } - idx, err := desc.FindIndexWithID(detail.Index.ID) - if err != nil { - return err - } - idx.IndexDesc().Name = newName - } else if detail.UniqueWithoutIndexConstraint != nil { - if detail.UniqueWithoutIndexConstraint.Validity == descpb.ConstraintValidity_Validating { - return unimplemented.NewWithIssueDetailf(42844, - "rename-constraint-unique-mutation", - "constraint %q in the middle of being added, try again later", - tree.ErrNameStringP(&detail.UniqueWithoutIndexConstraint.Name)) - } - detail.UniqueWithoutIndexConstraint.Name = newName - } else { - return errors.AssertionFailedf( - "Index or UniqueWithoutIndexConstraint must be non-nil for a unique constraint", - ) - } + u.IndexDesc().Name = newName return nil + } + switch constraint.GetConstraintValidity() { + case descpb.ConstraintValidity_Validating: + return unimplemented.NewWithIssuef(42844, + "constraint %q in the middle of being added, try again later", + tree.ErrNameString(constraint.GetName())) + } - case descpb.ConstraintTypeFK: - if detail.FK.Validity == descpb.ConstraintValidity_Validating { - return unimplemented.NewWithIssueDetailf(42844, - "rename-constraint-fk-mutation", - "constraint %q in the middle of being added, try again later", - tree.ErrNameStringP(&detail.FK.Name)) - } + if ck := constraint.AsCheck(); ck != nil { + ck.CheckDesc().Name = newName + } else if fk := constraint.AsForeignKey(); fk != nil { // Update the name on the referenced table descriptor. - if err := renameFK(desc, detail.FK, newName); err != nil { - return err - } - // Update the name on this table descriptor. - fk, err := desc.FindFKByName(detail.FK.Name) - if err != nil { + if err := renameFK(desc, fk, newName); err != nil { return err } - fk.Name = newName - return nil - - case descpb.ConstraintTypeCheck: - if detail.CheckConstraint.Validity == descpb.ConstraintValidity_Validating { - return unimplemented.NewWithIssueDetailf(42844, - "rename-constraint-check-mutation", - "constraint %q in the middle of being added, try again later", - tree.ErrNameStringP(&detail.CheckConstraint.Name)) - } - detail.CheckConstraint.Name = newName - return nil - - default: - return unimplemented.Newf(fmt.Sprintf("rename-constraint-%s", detail.Kind), - "constraint %q has unsupported type", tree.ErrNameString(oldName)) + fk.ForeignKeyDesc().Name = newName + } else if uwoi := constraint.AsUniqueWithoutIndex(); uwoi != nil { + uwoi.UniqueWithoutIndexDesc().Name = newName + } else { + return unimplemented.Newf(fmt.Sprintf("rename-constraint-%T", constraint), + "constraint %q has unsupported type", tree.ErrNameString(constraint.GetName())) } + return nil } // GetIndexMutationCapabilities implements the TableDescriptor interface. @@ -1977,15 +1764,15 @@ func (desc *Mutable) AddUniqueWithoutIndexMutation( // to add the new constraint name. // TODO(mgartner): Move this to schemaexpr.CheckConstraintBuilder. func MakeNotNullCheckConstraint( - colName string, - colID descpb.ColumnID, - constraintID descpb.ConstraintID, - inuseNames map[string]struct{}, - validity descpb.ConstraintValidity, + tbl catalog.TableDescriptor, col catalog.Column, validity descpb.ConstraintValidity, ) *descpb.TableDescriptor_CheckConstraint { - name := fmt.Sprintf("%s_auto_not_null", colName) + name := fmt.Sprintf("%s_auto_not_null", col.GetName()) // If generated name isn't unique, attempt to add a number to the end to // get a unique name, as in generateNameForCheckConstraint(). + inuseNames := make(map[string]struct{}) + for _, c := range tbl.AllConstraints() { + inuseNames[c.GetName()] = struct{}{} + } if _, ok := inuseNames[name]; ok { i := 1 for { @@ -1997,21 +1784,18 @@ func MakeNotNullCheckConstraint( i++ } } - if inuseNames != nil { - inuseNames[name] = struct{}{} - } expr := &tree.IsNotNullExpr{ - Expr: &tree.ColumnItem{ColumnName: tree.Name(colName)}, + Expr: &tree.ColumnItem{ColumnName: tree.Name(col.GetName())}, } return &descpb.TableDescriptor_CheckConstraint{ Name: name, Expr: tree.Serialize(expr), Validity: validity, - ColumnIDs: []descpb.ColumnID{colID}, + ColumnIDs: []descpb.ColumnID{col.GetID()}, IsNonNullConstraint: true, - ConstraintID: constraintID, + ConstraintID: tbl.GetNextConstraintID(), } } @@ -2194,6 +1978,7 @@ func (desc *Mutable) addIndexMutationMaybeWithTempIndex(m descpb.DescriptorMutat tempIndex.UseDeletePreservingEncoding = true tempIndex.ID = 0 tempIndex.Name = "" + tempIndex.ConstraintID = 0 m2 := descpb.DescriptorMutation{ Descriptor_: &descpb.DescriptorMutation_Index{Index: &tempIndex}, Direction: descpb.DescriptorMutation_ADD, @@ -2372,71 +2157,14 @@ func (desc *wrapper) TableSpan(codec keys.SQLCodec) roachpb.Span { return roachpb.Span{Key: prefix, EndKey: prefix.PrefixEnd()} } -// ColumnsUsed returns the IDs of the columns used in the check constraint's -// expression. v2.0 binaries will populate this during table creation, but older -// binaries will not, in which case this needs to be computed when requested. -// -// TODO(nvanbenschoten): we can remove this in v2.1 and replace it with a sql -// migration to backfill all descpb.TableDescriptor_CheckConstraint.ColumnIDs slices. -// See #22322. -func (desc *wrapper) ColumnsUsed( - cc *descpb.TableDescriptor_CheckConstraint, -) ([]descpb.ColumnID, error) { - if len(cc.ColumnIDs) > 0 { - // Already populated. - return cc.ColumnIDs, nil - } - - parsed, err := parser.ParseExpr(cc.Expr) - if err != nil { - return nil, pgerror.Wrapf(err, pgcode.Syntax, - "could not parse check constraint %s", cc.Expr) - } - - var colIDsUsed catalog.TableColSet - visitFn := func(expr tree.Expr) (recurse bool, newExpr tree.Expr, err error) { - if vBase, ok := expr.(tree.VarName); ok { - v, err := vBase.NormalizeVarName() - if err != nil { - return false, nil, err - } - if c, ok := v.(*tree.ColumnItem); ok { - col, err := desc.FindColumnWithName(c.ColumnName) - if err != nil || col.Dropped() { - return false, nil, pgerror.Newf(pgcode.UndefinedColumn, - "column %q not found for constraint %q", - c.ColumnName, parsed.String()) - } - colIDsUsed.Add(col.GetID()) - } - return false, v, nil - } - return true, expr, nil - } - if _, err := tree.SimpleVisit(parsed, visitFn); err != nil { - return nil, err - } - - cc.ColumnIDs = make([]descpb.ColumnID, 0, colIDsUsed.Len()) - for colID, ok := colIDsUsed.Next(0); ok; colID, ok = colIDsUsed.Next(colID + 1) { - cc.ColumnIDs = append(cc.ColumnIDs, colID) - } - sort.Sort(descpb.ColumnIDs(cc.ColumnIDs)) - return cc.ColumnIDs, nil -} - // CheckConstraintUsesColumn implements the TableDescriptor interface. func (desc *wrapper) CheckConstraintUsesColumn( cc *descpb.TableDescriptor_CheckConstraint, colID descpb.ColumnID, ) (bool, error) { - colsUsed, err := desc.ColumnsUsed(cc) - if err != nil { - return false, err - } - i := sort.Search(len(colsUsed), func(i int) bool { - return colsUsed[i] >= colID + i := sort.Search(len(cc.ColumnIDs), func(i int) bool { + return cc.ColumnIDs[i] >= colID }) - return i < len(colsUsed) && colsUsed[i] == colID, nil + return i < len(cc.ColumnIDs) && cc.ColumnIDs[i] == colID, nil } // GetFamilyOfColumn returns the ColumnFamilyDescriptor for the @@ -2499,11 +2227,6 @@ func (desc *wrapper) FindAllReferences() (map[descpb.ID]struct{}, error) { return refs, nil } -// ActiveChecks implements the TableDescriptor interface. -func (desc *immutable) ActiveChecks() []descpb.TableDescriptor_CheckConstraint { - return desc.allChecks -} - // IsShardColumn implements the TableDescriptor interface. func (desc *wrapper) IsShardColumn(col catalog.Column) bool { return nil != catalog.FindNonDropIndex(desc, func(idx catalog.Index) bool { diff --git a/pkg/sql/catalog/tabledesc/structured_test.go b/pkg/sql/catalog/tabledesc/structured_test.go index 80cf89414d1a..d353e7b6a90a 100644 --- a/pkg/sql/catalog/tabledesc/structured_test.go +++ b/pkg/sql/catalog/tabledesc/structured_test.go @@ -659,25 +659,16 @@ func TestUnvalidateConstraints(t *testing.T) { if err := desc.AllocateIDs(ctx, clusterversion.TestingClusterVersion); err != nil { t.Fatal(err) } - lookup := func(_ descpb.ID) (catalog.TableDescriptor, error) { - return desc.ImmutableCopy().(catalog.TableDescriptor), nil - } - before, err := desc.GetConstraintInfoWithLookup(lookup) - if err != nil { - t.Fatal(err) - } - if c, ok := before["fk"]; !ok || c.Unvalidated { - t.Fatalf("expected to find a validated constraint fk before, found %v", c) + before, _ := desc.FindConstraintWithName("fk") + if before == nil || !before.IsConstraintValidated() { + t.Fatalf("expected to find a validated constraint fk before, found %v", before) } desc.InvalidateFKConstraints() - after, err := desc.GetConstraintInfoWithLookup(lookup) - if err != nil { - t.Fatal(err) - } - if c, ok := after["fk"]; !ok || !c.Unvalidated { - t.Fatalf("expected to find an unvalidated constraint fk before, found %v", c) + after, _ := desc.FindConstraintWithName("fk") + if after == nil || before.IsConstraintValidated() { + t.Fatalf("expected to find an unvalidated constraint fk before, found %v", after) } } diff --git a/pkg/sql/catalog/tabledesc/table.go b/pkg/sql/catalog/tabledesc/table.go index 7c940badbe9c..f8133bbb32f0 100644 --- a/pkg/sql/catalog/tabledesc/table.go +++ b/pkg/sql/catalog/tabledesc/table.go @@ -30,7 +30,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/util/hlc" "github.com/cockroachdb/errors" - "github.com/cockroachdb/redact" ) // ColumnDefDescs contains the non-error return values for MakeColumnDefDescs. @@ -320,12 +319,7 @@ func (desc *wrapper) getExistingOrNewConstraintCache() *constraintCache { if desc.constraintCache != nil { return desc.constraintCache } - return newConstraintCache(desc.TableDesc(), desc.getExistingOrNewMutationCache()) -} - -// GetConstraintInfo implements the TableDescriptor interface. -func (desc *wrapper) GetConstraintInfo() (map[string]descpb.ConstraintDetail, error) { - return desc.collectConstraintInfo(nil) + return newConstraintCache(desc.TableDesc(), desc.getExistingOrNewIndexCache(), desc.getExistingOrNewMutationCache()) } // FindConstraintWithID implements the TableDescriptor interface. @@ -339,172 +333,75 @@ func (desc *wrapper) FindConstraintWithID(id descpb.ConstraintID) (catalog.Const return nil, pgerror.Newf(pgcode.UndefinedObject, "constraint-id \"%d\" does not exist", id) } +// FindConstraintWithName implements the TableDescriptor interface. +func (desc *wrapper) FindConstraintWithName(name string) (catalog.Constraint, error) { + all := desc.AllConstraints() + for _, c := range all { + if c.GetName() == name { + return c, nil + } + } + return nil, pgerror.Newf(pgcode.UndefinedObject, "constraint named %q does not exist", name) +} + // AllConstraints implements the catalog.TableDescriptor interface. func (desc *wrapper) AllConstraints() []catalog.Constraint { return desc.getExistingOrNewConstraintCache().all } -// AllActiveAndInactiveConstraints implements the catalog.TableDescriptor interface. -func (desc *wrapper) AllActiveAndInactiveConstraints() []catalog.Constraint { - return desc.getExistingOrNewConstraintCache().allActiveAndInactive +// EnforcedConstraints implements the catalog.TableDescriptor interface. +func (desc *wrapper) EnforcedConstraints() []catalog.Constraint { + return desc.getExistingOrNewConstraintCache().allEnforced } -// AllActiveConstraints implements the catalog.TableDescriptor interface. -func (desc *wrapper) AllActiveConstraints() []catalog.Constraint { - return desc.getExistingOrNewConstraintCache().allActive +// CheckConstraints implements the catalog.TableDescriptor interface. +func (desc *wrapper) CheckConstraints() []catalog.CheckConstraint { + return desc.getExistingOrNewConstraintCache().checks } -// GetConstraintInfoWithLookup implements the TableDescriptor interface. -func (desc *wrapper) GetConstraintInfoWithLookup( - tableLookup catalog.TableLookupFn, -) (map[string]descpb.ConstraintDetail, error) { - return desc.collectConstraintInfo(tableLookup) +// EnforcedCheckConstraints implements the catalog.TableDescriptor interface. +func (desc *wrapper) EnforcedCheckConstraints() []catalog.CheckConstraint { + return desc.getExistingOrNewConstraintCache().checksEnforced } -// CheckUniqueConstraints returns a non-nil error if a descriptor contains two -// constraints with the same name. -func (desc *wrapper) CheckUniqueConstraints() error { - _, err := desc.collectConstraintInfo(nil) - return err +// OutboundForeignKeys implements the catalog.TableDescriptor interface. +func (desc *wrapper) OutboundForeignKeys() []catalog.ForeignKeyConstraint { + return desc.getExistingOrNewConstraintCache().fks } -// if `tableLookup` is non-nil, provide a full summary of constraints, otherwise just -// check that constraints have unique names. -func (desc *wrapper) collectConstraintInfo( - tableLookup catalog.TableLookupFn, -) (map[string]descpb.ConstraintDetail, error) { - info := make(map[string]descpb.ConstraintDetail) - - // Indexes provide PK and Unique constraints that are enforced by an index. - for _, indexI := range desc.NonDropIndexes() { - index := indexI.IndexDesc() - if index.ID == desc.PrimaryIndex.ID { - if _, ok := info[index.Name]; ok { - return nil, pgerror.Newf(pgcode.DuplicateObject, - "duplicate constraint name: %q", index.Name) - } - indexName := index.Name - // If a primary key swap is occurring, then the primary index name can - // be seen as being under the new name. - for _, mutation := range desc.GetMutations() { - if mutation.GetPrimaryKeySwap() != nil { - indexName = mutation.GetPrimaryKeySwap().NewPrimaryIndexName - } - } - detail := descpb.ConstraintDetail{ - Kind: descpb.ConstraintTypePK, - ConstraintID: index.ConstraintID, - } - detail.Columns = index.KeyColumnNames - detail.Index = index - info[indexName] = detail - } else if index.Unique { - if _, ok := info[index.Name]; ok { - return nil, pgerror.Newf(pgcode.DuplicateObject, - "duplicate constraint name: %q", index.Name) - } - detail := descpb.ConstraintDetail{ - Kind: descpb.ConstraintTypeUnique, - ConstraintID: index.ConstraintID, - } - detail.Columns = index.KeyColumnNames - detail.Index = index - info[index.Name] = detail - } - } +// EnforcedOutboundForeignKeys implements the catalog.TableDescriptor +// interface. +func (desc *wrapper) EnforcedOutboundForeignKeys() []catalog.ForeignKeyConstraint { + return desc.getExistingOrNewConstraintCache().fksEnforced +} - // Get the unique constraints that are not enforced by an index. - ucs := desc.AllActiveAndInactiveUniqueWithoutIndexConstraints() - for _, uc := range ucs { - if _, ok := info[uc.Name]; ok { - return nil, pgerror.Newf(pgcode.DuplicateObject, - "duplicate constraint name: %q", uc.Name) - } - detail := descpb.ConstraintDetail{ - Kind: descpb.ConstraintTypeUnique, - ConstraintID: uc.ConstraintID, - } - // Constraints in the Validating state are considered Unvalidated for this - // purpose. - detail.Unvalidated = uc.Validity != descpb.ConstraintValidity_Validated - var err error - detail.Columns, err = desc.NamesForColumnIDs(uc.ColumnIDs) - if err != nil { - return nil, err - } - detail.UniqueWithoutIndexConstraint = uc - info[uc.Name] = detail - } +// InboundForeignKeys implements the catalog.TableDescriptor interface. +func (desc *wrapper) InboundForeignKeys() []catalog.ForeignKeyConstraint { + return desc.getExistingOrNewConstraintCache().fkBackRefs +} - fks := desc.AllActiveAndInactiveForeignKeys() - for _, fk := range fks { - if _, ok := info[fk.Name]; ok { - return nil, pgerror.Newf(pgcode.DuplicateObject, - "duplicate constraint name: %q", fk.Name) - } - detail := descpb.ConstraintDetail{ - Kind: descpb.ConstraintTypeFK, - ConstraintID: fk.ConstraintID, - } - // Constraints in the Validating state are considered Unvalidated for this - // purpose. - detail.Unvalidated = fk.Validity != descpb.ConstraintValidity_Validated - var err error - detail.Columns, err = desc.NamesForColumnIDs(fk.OriginColumnIDs) - if err != nil { - return nil, err - } - detail.FK = fk +// UniqueConstraintsWithIndex implements the catalog.TableDescriptor +// interface. +func (desc *wrapper) UniqueConstraintsWithIndex() []catalog.UniqueWithIndexConstraint { + return desc.getExistingOrNewConstraintCache().uwis +} - if tableLookup != nil { - other, err := tableLookup(fk.ReferencedTableID) - if err != nil { - return nil, errors.NewAssertionErrorWithWrappedErrf(err, - "error resolving table %d referenced in foreign key", - redact.Safe(fk.ReferencedTableID)) - } - referencedColumnNames, err := other.NamesForColumnIDs(fk.ReferencedColumnIDs) - if err != nil { - return nil, err - } - detail.Details = fmt.Sprintf("%s.%v", other.GetName(), referencedColumnNames) - detail.ReferencedTable = other.TableDesc() - } - info[fk.Name] = detail - } +// EnforcedUniqueConstraintsWithIndex implements the catalog.TableDescriptor +// interface. +func (desc *wrapper) EnforcedUniqueConstraintsWithIndex() []catalog.UniqueWithIndexConstraint { + return desc.getExistingOrNewConstraintCache().uwisEnforced +} - for _, c := range desc.AllActiveAndInactiveChecks() { - if _, ok := info[c.Name]; ok { - return nil, pgerror.Newf(pgcode.DuplicateObject, - "duplicate constraint name: %q", c.Name) - } - detail := descpb.ConstraintDetail{ - Kind: descpb.ConstraintTypeCheck, - ConstraintID: c.ConstraintID, - } - // Constraints in the Validating state are considered Unvalidated for this - // purpose. - detail.Unvalidated = c.Validity != descpb.ConstraintValidity_Validated - detail.CheckConstraint = c - detail.Details = c.Expr - if tableLookup != nil { - colsUsed, err := desc.ColumnsUsed(c) - if err != nil { - return nil, errors.NewAssertionErrorWithWrappedErrf(err, - "error computing columns used in check constraint %q", c.Name) - } - for _, colID := range colsUsed { - col, err := desc.FindColumnWithID(colID) - if err != nil { - return nil, errors.NewAssertionErrorWithWrappedErrf(err, - "error finding column %d in table %s", redact.Safe(colID), desc.Name) - } - detail.Columns = append(detail.Columns, col.GetName()) - } - } - info[c.Name] = detail - } - return info, nil +// UniqueConstraintsWithoutIndex implements the catalog.TableDescriptor +// interface. +func (desc *wrapper) UniqueConstraintsWithoutIndex() []catalog.UniqueWithoutIndexConstraint { + return desc.getExistingOrNewConstraintCache().uwois +} + +// EnforcedUniqueConstraintsWithoutIndex implements the catalog.TableDescriptor +// interface. +func (desc *wrapper) EnforcedUniqueConstraintsWithoutIndex() []catalog.UniqueWithoutIndexConstraint { + return desc.getExistingOrNewConstraintCache().uwoisEnforced } // FindFKReferencedUniqueConstraint finds the first index in the supplied @@ -513,26 +410,16 @@ func (desc *wrapper) collectConstraintInfo( // column ids. If neither an index nor unique constraint is found, returns an // error. func FindFKReferencedUniqueConstraint( - referencedTable catalog.TableDescriptor, referencedColIDs descpb.ColumnIDs, -) (descpb.UniqueConstraint, error) { - // Search for a unique index on the referenced table that matches our foreign - // key columns. - primaryIndex := referencedTable.GetPrimaryIndex() - if primaryIndex.IsValidReferencedUniqueConstraint(referencedColIDs) { - return primaryIndex.IndexDesc(), nil - } - // If the PK doesn't match, find the index corresponding to the referenced column. - for _, idx := range referencedTable.PublicNonPrimaryIndexes() { - if idx.IsValidReferencedUniqueConstraint(referencedColIDs) { - return idx.IndexDesc(), nil - } - } - // As a last resort, try to find a unique constraint with matching columns. - uniqueWithoutIndexConstraints := referencedTable.GetUniqueWithoutIndexConstraints() - for i := range uniqueWithoutIndexConstraints { - c := &uniqueWithoutIndexConstraints[i] - if c.IsValidReferencedUniqueConstraint(referencedColIDs) { - return c, nil + referencedTable catalog.TableDescriptor, fk catalog.ForeignKeyConstraint, +) (catalog.UniqueConstraint, error) { + for _, uwi := range referencedTable.UniqueConstraintsWithIndex() { + if !uwi.Dropped() && uwi.IsValidReferencedUniqueConstraint(fk) { + return uwi, nil + } + } + for _, uwoi := range referencedTable.UniqueConstraintsWithoutIndex() { + if !uwoi.Dropped() && uwoi.IsValidReferencedUniqueConstraint(fk) { + return uwoi, nil } } return nil, pgerror.Newf( diff --git a/pkg/sql/catalog/tabledesc/table_desc.go b/pkg/sql/catalog/tabledesc/table_desc.go index babcf9737101..fb5224dba964 100644 --- a/pkg/sql/catalog/tabledesc/table_desc.go +++ b/pkg/sql/catalog/tabledesc/table_desc.go @@ -83,30 +83,15 @@ func (desc *wrapper) SkipNamespace() bool { return false } -// ActiveChecks implements the TableDescriptor interface. -func (desc *wrapper) ActiveChecks() []descpb.TableDescriptor_CheckConstraint { - checks := make([]descpb.TableDescriptor_CheckConstraint, len(desc.Checks)) - for i, c := range desc.Checks { - checks[i] = *c - } - return checks -} - // immutable is a custom type for TableDescriptors // It holds precomputed values and the underlying TableDescriptor // should be const. type immutable struct { wrapper - allChecks []descpb.TableDescriptor_CheckConstraint - // isUncommittedVersion is set to true if this descriptor was created from // a copy of a Mutable with an uncommitted version. isUncommittedVersion bool - - // TODO (lucy): populate these and use them - // inboundFKs []*ForeignKeyConstraint - // outboundFKs []*ForeignKeyConstraint } // GetRawBytesInStorage implements the catalog.Descriptor interface. @@ -290,7 +275,7 @@ func UpdateIndexPartitioning( } // GetPrimaryIndex implements the TableDescriptor interface. -func (desc *wrapper) GetPrimaryIndex() catalog.Index { +func (desc *wrapper) GetPrimaryIndex() catalog.UniqueWithIndexConstraint { return desc.getExistingOrNewIndexCache().primary } @@ -563,6 +548,60 @@ func (desc *wrapper) IndexStoredColumns(idx catalog.Index) []catalog.Column { return nil } +// CheckConstraintColumns implements the TableDescriptor interface. +func (desc *wrapper) CheckConstraintColumns(ck catalog.CheckConstraint) []catalog.Column { + n := ck.NumReferencedColumns() + if n == 0 { + return nil + } + ret := make([]catalog.Column, n) + for i := 0; i < n; i++ { + ret[i], _ = desc.FindColumnWithID(ck.GetReferencedColumnID(i)) + } + return ret +} + +// ForeignKeyReferencedColumns implements the TableDescriptor interface. +func (desc *wrapper) ForeignKeyReferencedColumns(fk catalog.ForeignKeyConstraint) []catalog.Column { + n := fk.NumReferencedColumns() + if fk.GetReferencedTableID() != desc.GetID() || n == 0 { + return nil + } + ret := make([]catalog.Column, n) + for i := 0; i < n; i++ { + ret[i], _ = desc.FindColumnWithID(fk.GetReferencedColumnID(i)) + } + return ret +} + +// ForeignKeyOriginColumns implements the TableDescriptor interface. +func (desc *wrapper) ForeignKeyOriginColumns(fk catalog.ForeignKeyConstraint) []catalog.Column { + n := fk.NumOriginColumns() + if fk.GetOriginTableID() != desc.GetID() || n == 0 { + return nil + } + ret := make([]catalog.Column, n) + for i := 0; i < n; i++ { + ret[i], _ = desc.FindColumnWithID(fk.GetOriginColumnID(i)) + } + return ret +} + +// UniqueWithoutIndexColumns implements the TableDescriptor interface. +func (desc *wrapper) UniqueWithoutIndexColumns( + uwoi catalog.UniqueWithoutIndexConstraint, +) []catalog.Column { + n := uwoi.NumKeyColumns() + if uwoi.ParentTableID() != desc.GetID() || n == 0 { + return nil + } + ret := make([]catalog.Column, n) + for i := 0; i < n; i++ { + ret[i], _ = desc.FindColumnWithID(uwoi.GetKeyColumnID(i)) + } + return ret +} + // IndexFetchSpecKeyAndSuffixColumns implements the TableDescriptor interface. func (desc *wrapper) IndexFetchSpecKeyAndSuffixColumns( idx catalog.Index, diff --git a/pkg/sql/catalog/tabledesc/table_desc_builder.go b/pkg/sql/catalog/tabledesc/table_desc_builder.go index 15fca0a866ea..b09b374f2e09 100644 --- a/pkg/sql/catalog/tabledesc/table_desc_builder.go +++ b/pkg/sql/catalog/tabledesc/table_desc_builder.go @@ -247,13 +247,7 @@ func makeImmutable(tbl *descpb.TableDescriptor) *immutable { desc.mutationCache = newMutationCache(desc.TableDesc()) desc.indexCache = newIndexCache(desc.TableDesc(), desc.mutationCache) desc.columnCache = newColumnCache(desc.TableDesc(), desc.mutationCache) - desc.constraintCache = newConstraintCache(desc.TableDesc(), desc.mutationCache) - - desc.allChecks = make([]descpb.TableDescriptor_CheckConstraint, len(tbl.Checks)) - for i, c := range tbl.Checks { - desc.allChecks[i] = *c - } - + desc.constraintCache = newConstraintCache(desc.TableDesc(), desc.indexCache, desc.mutationCache) return &desc } @@ -311,6 +305,7 @@ func maybeFillInDescriptor( set(catalog.UpgradedPrivileges, fixedPrivileges) set(catalog.RemovedDuplicateIDsInRefs, maybeRemoveDuplicateIDsInRefs(desc)) set(catalog.AddedConstraintIDs, maybeAddConstraintIDs(desc)) + set(catalog.SetCheckConstraintColumnIDs, maybeSetCheckConstraintColumnIDs(desc)) return changes, nil } @@ -469,21 +464,18 @@ func maybeUpgradeForeignKeyRepOnIndex( // reference, or the other table was upgraded. Assume the second for now. // If we also find no matching reference in the new-style foreign keys, // that indicates a corrupt reference. - var forwardFK *descpb.ForeignKeyConstraint - _ = otherTable.ForeachOutboundFK(func(otherFK *descpb.ForeignKeyConstraint) error { - if forwardFK != nil { - return nil - } + var forwardFK catalog.ForeignKeyConstraint + for _, otherFK := range otherTable.OutboundForeignKeys() { // To find a match, we find a foreign key reference that has the same // referenced table ID, and that the index we point to is a valid // index to satisfy the columns in the foreign key. - if otherFK.ReferencedTableID == desc.ID && - descpb.ColumnIDs(originIndex.KeyColumnIDs).HasPrefix(otherFK.OriginColumnIDs) { + if otherFK.GetReferencedTableID() == desc.ID && + descpb.ColumnIDs(originIndex.KeyColumnIDs).HasPrefix(otherFK.ForeignKeyDesc().OriginColumnIDs) { // Found a match. forwardFK = otherFK + break } - return nil - }) + } if forwardFK == nil { // Corrupted foreign key - there was no forward reference for the back // reference. @@ -493,14 +485,14 @@ func maybeUpgradeForeignKeyRepOnIndex( } inFK = descpb.ForeignKeyConstraint{ OriginTableID: ref.Table, - OriginColumnIDs: forwardFK.OriginColumnIDs, + OriginColumnIDs: forwardFK.ForeignKeyDesc().OriginColumnIDs, ReferencedTableID: desc.ID, - ReferencedColumnIDs: forwardFK.ReferencedColumnIDs, - Name: forwardFK.Name, - Validity: forwardFK.Validity, - OnDelete: forwardFK.OnDelete, - OnUpdate: forwardFK.OnUpdate, - Match: forwardFK.Match, + ReferencedColumnIDs: forwardFK.ForeignKeyDesc().ReferencedColumnIDs, + Name: forwardFK.GetName(), + Validity: forwardFK.GetConstraintValidity(), + OnDelete: forwardFK.OnDelete(), + OnUpdate: forwardFK.OnUpdate(), + Match: forwardFK.Match(), ConstraintID: desc.GetNextConstraintID(), } } else { @@ -751,91 +743,147 @@ func maybeAddConstraintIDs(desc *descpb.TableDescriptor) (hasChanged bool) { if !desc.IsTable() { return false } - initialConstraintID := desc.NextConstraintID - // Maps index IDs to indexes for one which have - // a constraint ID assigned. - constraintIndexes := make(map[descpb.IndexID]*descpb.IndexDescriptor) - if desc.NextConstraintID == 0 { - desc.NextConstraintID = 1 - } - nextConstraintID := func() descpb.ConstraintID { - id := desc.GetNextConstraintID() - desc.NextConstraintID++ - return id - } - // Loop over all constraints and assign constraint IDs. - if desc.PrimaryIndex.ConstraintID == 0 { - desc.PrimaryIndex.ConstraintID = nextConstraintID() - constraintIndexes[desc.PrimaryIndex.ID] = &desc.PrimaryIndex + // Collect pointers to constraint ID variables. + var idPtrs []*descpb.ConstraintID + if len(desc.PrimaryIndex.KeyColumnIDs) > 0 { + idPtrs = append(idPtrs, &desc.PrimaryIndex.ConstraintID) } for i := range desc.Indexes { idx := &desc.Indexes[i] - if idx.Unique && idx.ConstraintID == 0 { - idx.ConstraintID = nextConstraintID() - constraintIndexes[idx.ID] = idx + if !idx.Unique || idx.UseDeletePreservingEncoding { + continue } + idPtrs = append(idPtrs, &idx.ConstraintID) } + checkByName := make(map[string]*descpb.TableDescriptor_CheckConstraint) for i := range desc.Checks { - check := desc.Checks[i] - if check.ConstraintID == 0 { - check.ConstraintID = nextConstraintID() - } + ck := desc.Checks[i] + idPtrs = append(idPtrs, &ck.ConstraintID) + checkByName[ck.Name] = ck + } + fkByName := make(map[string]*descpb.ForeignKeyConstraint) + for i := range desc.OutboundFKs { + fk := &desc.OutboundFKs[i] + idPtrs = append(idPtrs, &fk.ConstraintID) + fkByName[fk.Name] = fk } for i := range desc.InboundFKs { - fk := &desc.InboundFKs[i] - if fk.ConstraintID == 0 { - fk.ConstraintID = nextConstraintID() + idPtrs = append(idPtrs, &desc.InboundFKs[i].ConstraintID) + } + uwoiByName := make(map[string]*descpb.UniqueWithoutIndexConstraint) + for i := range desc.UniqueWithoutIndexConstraints { + uwoi := &desc.UniqueWithoutIndexConstraints[i] + idPtrs = append(idPtrs, &uwoi.ConstraintID) + uwoiByName[uwoi.Name] = uwoi + } + for _, m := range desc.GetMutations() { + if idx := m.GetIndex(); idx != nil && idx.Unique && !idx.UseDeletePreservingEncoding { + idPtrs = append(idPtrs, &idx.ConstraintID) + } else if c := m.GetConstraint(); c != nil { + switch c.ConstraintType { + case descpb.ConstraintToUpdate_CHECK, descpb.ConstraintToUpdate_NOT_NULL: + idPtrs = append(idPtrs, &c.Check.ConstraintID) + case descpb.ConstraintToUpdate_FOREIGN_KEY: + idPtrs = append(idPtrs, &c.ForeignKey.ConstraintID) + case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: + idPtrs = append(idPtrs, &c.UniqueWithoutIndexConstraint.ConstraintID) + } } } - for i := range desc.OutboundFKs { - fk := &desc.OutboundFKs[i] - if fk.ConstraintID == 0 { - fk.ConstraintID = nextConstraintID() + // Set constraint ID counter to sane initial value. + var maxID descpb.ConstraintID + for _, p := range idPtrs { + if id := *p; id > maxID { + maxID = id } } - for i := range desc.UniqueWithoutIndexConstraints { - unique := desc.UniqueWithoutIndexConstraints[i] - if unique.ConstraintID == 0 { - unique.ConstraintID = nextConstraintID() + if desc.NextConstraintID <= maxID { + desc.NextConstraintID = maxID + 1 + hasChanged = true + } + // Update zero constraint IDs using counter. + for _, p := range idPtrs { + if *p != 0 { + continue } + *p = desc.NextConstraintID + desc.NextConstraintID++ + hasChanged = true } - // Update mutations to add the constraint ID. In the case of a PK swap - // we may need to maintain the same constraint ID. - for _, mutation := range desc.GetMutations() { - if idx := mutation.GetIndex(); idx != nil && - idx.ConstraintID == 0 && - mutation.Direction == descpb.DescriptorMutation_ADD && - idx.Unique { - idx.ConstraintID = nextConstraintID() - constraintIndexes[idx.ID] = idx - } else if pkSwap := mutation.GetPrimaryKeySwap(); pkSwap != nil { - for idx := range pkSwap.NewIndexes { - oldIdx, firstOk := constraintIndexes[pkSwap.OldIndexes[idx]] - newIdx := constraintIndexes[pkSwap.NewIndexes[idx]] - if !firstOk { - continue - } - newIdx.ConstraintID = oldIdx.ConstraintID - } - } else if constraint := mutation.GetConstraint(); constraint != nil { - switch constraint.ConstraintType { - case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: - if constraint.UniqueWithoutIndexConstraint.ConstraintID == 0 { - constraint.UniqueWithoutIndexConstraint.ConstraintID = nextConstraintID() - } + // Reconcile constraint IDs between enforced slice and mutation. + for _, m := range desc.GetMutations() { + if c := m.GetConstraint(); c != nil { + switch c.ConstraintType { case descpb.ConstraintToUpdate_CHECK, descpb.ConstraintToUpdate_NOT_NULL: - if constraint.Check.ConstraintID == 0 { - constraint.Check.ConstraintID = nextConstraintID() + if other, ok := checkByName[c.Check.Name]; ok { + c.Check.ConstraintID = other.ConstraintID } case descpb.ConstraintToUpdate_FOREIGN_KEY: - if constraint.ForeignKey.ConstraintID == 0 { - constraint.ForeignKey.ConstraintID = nextConstraintID() + if other, ok := fkByName[c.ForeignKey.Name]; ok { + c.ForeignKey.ConstraintID = other.ConstraintID + } + case descpb.ConstraintToUpdate_UNIQUE_WITHOUT_INDEX: + if other, ok := uwoiByName[c.UniqueWithoutIndexConstraint.Name]; ok { + c.UniqueWithoutIndexConstraint.ConstraintID = other.ConstraintID } } } + } + return hasChanged +} +// maybeSetCheckConstraintColumnIDs ensures that all check constraints have a +// ColumnIDs slice which is populated if it should be. +func maybeSetCheckConstraintColumnIDs(desc *descpb.TableDescriptor) (hasChanged bool) { + // Collect valid column names. + nonDropColumnIDs := make(map[string]descpb.ColumnID, len(desc.Columns)) + for i := range desc.Columns { + nonDropColumnIDs[desc.Columns[i].Name] = desc.Columns[i].ID } - return desc.NextConstraintID != initialConstraintID + for _, m := range desc.Mutations { + if col := m.GetColumn(); col != nil && m.Direction != descpb.DescriptorMutation_DROP { + nonDropColumnIDs[col.Name] = col.ID + } + } + var colIDsUsed catalog.TableColSet + visitFn := func(expr tree.Expr) (recurse bool, newExpr tree.Expr, err error) { + if vBase, ok := expr.(tree.VarName); ok { + v, err := vBase.NormalizeVarName() + if err != nil { + return false, nil, err + } + if c, ok := v.(*tree.ColumnItem); ok { + colID, found := nonDropColumnIDs[string(c.ColumnName)] + if !found { + return false, nil, errors.New("column not found") + } + colIDsUsed.Add(colID) + } + return false, v, nil + } + return true, expr, nil + } + + for _, ck := range desc.Checks { + if len(ck.ColumnIDs) > 0 { + continue + } + parsed, err := parser.ParseExpr(ck.Expr) + if err != nil { + // We do this on a best-effort basis. + continue + } + colIDsUsed = catalog.TableColSet{} + if _, err := tree.SimpleVisit(parsed, visitFn); err != nil { + // We do this on a best-effort basis. + continue + } + if !colIDsUsed.Empty() { + ck.ColumnIDs = colIDsUsed.Ordered() + hasChanged = true + } + } + return hasChanged } // maybeSetCreateAsOfTime ensures that the CreateAsOfTime field is set. diff --git a/pkg/sql/catalog/tabledesc/validate.go b/pkg/sql/catalog/tabledesc/validate.go index a9123c86eabc..c0fb5ef58e08 100644 --- a/pkg/sql/catalog/tabledesc/validate.go +++ b/pkg/sql/catalog/tabledesc/validate.go @@ -11,8 +11,6 @@ package tabledesc import ( - "sort" - "github.com/cockroachdb/cockroach/pkg/keys" "github.com/cockroachdb/cockroach/pkg/roachpb" "github.com/cockroachdb/cockroach/pkg/settings" @@ -435,15 +433,10 @@ func (desc *wrapper) validateOutboundFKBackReference( return nil } - found := false - _ = referencedTable.ForeachInboundFK(func(backref *descpb.ForeignKeyConstraint) error { - if !found && backref.OriginTableID == desc.ID && backref.Name == fk.Name { - found = true + for _, backref := range referencedTable.InboundForeignKeys() { + if backref.GetOriginTableID() == desc.ID && backref.GetName() == fk.Name { + return nil } - return nil - }) - if found { - return nil } return errors.AssertionFailedf("missing fk back reference %q to %q from %q", fk.Name, desc.Name, referencedTable.GetName()) @@ -461,15 +454,10 @@ func (desc *wrapper) validateInboundFK( return errors.AssertionFailedf("origin table %q (%d) is dropped", originTable.GetName(), originTable.GetID()) } - found := false - _ = originTable.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - if !found && fk.ReferencedTableID == desc.ID && fk.Name == backref.Name { - found = true + for _, fk := range originTable.OutboundForeignKeys() { + if fk.GetReferencedTableID() == desc.ID && fk.GetName() == backref.Name { + return nil } - return nil - }) - if found { - return nil } return errors.AssertionFailedf("missing fk forward reference %q to %q from %q", backref.Name, desc.Name, originTable.GetName()) @@ -668,11 +656,6 @@ func (desc *wrapper) ValidateSelf(vea catalog.ValidationErrorAccumulator) { return } - if err := desc.CheckUniqueConstraints(); err != nil { - vea.Report(err) - return - } - // Validate mutations and exit early if any of these are deeply corrupted. { var mutationIDs util.FastIntSet @@ -719,11 +702,12 @@ func (desc *wrapper) ValidateSelf(vea catalog.ValidationErrorAccumulator) { // Only validate column families, constraints, and indexes if this is // actually a table, not if it's just a view. if desc.IsPhysicalTable() { + desc.validateConstraintNamesAndIDs(vea) newErrs := []error{ desc.validateColumnFamilies(columnsByID), desc.validateCheckConstraints(columnsByID), desc.validateUniqueWithoutIndexConstraints(columnsByID), - desc.validateTableIndexes(columnsByID, vea), + desc.validateTableIndexes(columnsByID), desc.validatePartitioning(), } hasErrs := false @@ -736,7 +720,7 @@ func (desc *wrapper) ValidateSelf(vea catalog.ValidationErrorAccumulator) { if hasErrs { return } - desc.validateConstraintIDs(vea) + } // Ensure that mutations cannot be queued if a primary key change, TTL change @@ -865,15 +849,20 @@ func ValidateNotVisibleIndex( return notices } - notice := tableDesc.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - // Case 2: The given index is an index on a child table that may be useful - // for FK check. - if index.IsHelpfulOriginIndex(fk.OriginColumnIDs) { - return notices - } + if index.IsPartial() || index.NumKeyColumns() == 0 { return nil - }) - return notice + } + firstKeyColID := index.GetKeyColumnID(0) + for _, fk := range tableDesc.OutboundForeignKeys() { + for i, n := 0, fk.NumOriginColumns(); i < n; i++ { + if fk.GetOriginColumnID(i) == firstKeyColID { + // Case 2: The given index is an index on a child table that may be useful + // for FK check. + return notices + } + } + } + return nil } // ValidateOnUpdate returns an error if there is a column with both a foreign @@ -886,53 +875,64 @@ func ValidateOnUpdate(desc catalog.TableDescriptor, errReportFn func(err error)) } } - _ = desc.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - if fk.OnUpdate == catpb.ForeignKeyAction_NO_ACTION || - fk.OnUpdate == catpb.ForeignKeyAction_RESTRICT { - return nil + for _, fk := range desc.OutboundForeignKeys() { + if fk.OnUpdate() == catpb.ForeignKeyAction_NO_ACTION || + fk.OnUpdate() == catpb.ForeignKeyAction_RESTRICT { + continue } - for _, fkCol := range fk.OriginColumnIDs { + for i, n := 0, fk.NumOriginColumns(); i < n; i++ { + fkCol := fk.GetOriginColumnID(i) if onUpdateCols.Contains(fkCol) { col, err := desc.FindColumnWithID(fkCol) if err != nil { - return err + errReportFn(err) + } else { + errReportFn(pgerror.Newf(pgcode.InvalidTableDefinition, + "cannot specify both ON UPDATE expression and a foreign key"+ + " ON UPDATE action for column %q", + col.ColName(), + )) } - errReportFn(pgerror.Newf(pgcode.InvalidTableDefinition, - "cannot specify both ON UPDATE expression and a foreign key"+ - " ON UPDATE action for column %q", - col.ColName(), - )) } } - return nil - }) + } } -func (desc *wrapper) validateConstraintIDs(vea catalog.ValidationErrorAccumulator) { +func (desc *wrapper) validateConstraintNamesAndIDs(vea catalog.ValidationErrorAccumulator) { if !desc.IsTable() { return } - constraints, err := desc.GetConstraintInfo() - if err != nil { - vea.Report(err) - return - } - // Sort the names to get deterministic behaviour, since - // constraints are stored in a map. - orderedNames := make([]string, 0, len(constraints)) - for name := range constraints { - orderedNames = append(orderedNames, name) - } - sort.Strings(orderedNames) - for _, name := range orderedNames { - constraint := constraints[name] - if constraint.ConstraintID == 0 { - vea.Report(errors.AssertionFailedf("constraint ID was missing for constraint: %s with name %q", - constraint.Kind, - name)) - + constraints := desc.AllConstraints() + names := make(map[string]descpb.ConstraintID, len(constraints)) + idToName := make(map[descpb.ConstraintID]string, len(constraints)) + for _, c := range constraints { + if c.GetConstraintID() == 0 { + vea.Report(errors.AssertionFailedf( + "constraint ID was missing for constraint %q", + c.GetName())) + } else if c.GetConstraintID() >= desc.NextConstraintID { + vea.Report(errors.AssertionFailedf( + "constraint %q has ID %d not less than NextConstraintID value %d for table", + c.GetName(), c.GetConstraintID(), desc.NextConstraintID)) + } + if c.GetName() == "" { + vea.Report(pgerror.Newf(pgcode.Syntax, "empty constraint name")) } + if !c.Dropped() { + if otherID, found := names[c.GetName()]; found && c.GetConstraintID() != otherID { + vea.Report(pgerror.Newf(pgcode.DuplicateObject, + "duplicate constraint name: %q", c.GetName())) + } + names[c.GetName()] = c.GetConstraintID() + } + if other, found := idToName[c.GetConstraintID()]; found { + vea.Report(pgerror.Newf(pgcode.DuplicateObject, + "constraint ID %d in constraint %q already in use by %q", + c.GetConstraintID(), c.GetName(), other)) + } + idToName[c.GetConstraintID()] = c.GetName() } + } func (desc *wrapper) validateColumns() error { @@ -1114,17 +1114,18 @@ func (desc *wrapper) validateColumnFamilies(columnsByID map[descpb.ColumnID]cata func (desc *wrapper) validateCheckConstraints( columnsByID map[descpb.ColumnID]catalog.Column, ) error { - for _, chk := range desc.AllActiveAndInactiveChecks() { + for _, chk := range desc.CheckConstraints() { // Verify that the check's column IDs are valid. - for _, colID := range chk.ColumnIDs { + for i, n := 0, chk.NumReferencedColumns(); i < n; i++ { + colID := chk.GetReferencedColumnID(i) _, ok := columnsByID[colID] if !ok { - return errors.Newf("check constraint %q contains unknown column \"%d\"", chk.Name, colID) + return errors.Newf("check constraint %q contains unknown column \"%d\"", chk.GetName(), colID) } } // Verify that the check's expression is valid. - expr, err := parser.ParseExpr(chk.Expr) + expr, err := parser.ParseExpr(chk.GetExpr()) if err != nil { return err } @@ -1134,7 +1135,7 @@ func (desc *wrapper) validateCheckConstraints( } if !valid { return errors.Newf("check constraint %q refers to unknown columns in expression: %s", - chk.Name, chk.Expr) + chk.GetName(), chk.GetExpr()) } } return nil @@ -1146,38 +1147,39 @@ func (desc *wrapper) validateCheckConstraints( func (desc *wrapper) validateUniqueWithoutIndexConstraints( columnsByID map[descpb.ColumnID]catalog.Column, ) error { - for _, c := range desc.AllActiveAndInactiveUniqueWithoutIndexConstraints() { - if len(c.Name) == 0 { + for _, c := range desc.UniqueConstraintsWithoutIndex() { + if len(c.GetName()) == 0 { return pgerror.Newf(pgcode.Syntax, "empty unique without index constraint name") } // Verify that the table ID is valid. - if c.TableID != desc.ID { + if c.ParentTableID() != desc.ID { return errors.Newf( "TableID mismatch for unique without index constraint %q: \"%d\" doesn't match descriptor: \"%d\"", - c.Name, c.TableID, desc.ID, + c.GetName(), c.ParentTableID(), desc.ID, ) } // Verify that the constraint's column IDs are valid and unique. var seen util.FastIntSet - for _, colID := range c.ColumnIDs { + for i, n := 0, c.NumKeyColumns(); i < n; i++ { + colID := c.GetKeyColumnID(i) _, ok := columnsByID[colID] if !ok { return errors.Newf( - "unique without index constraint %q contains unknown column \"%d\"", c.Name, colID, + "unique without index constraint %q contains unknown column \"%d\"", c.GetName(), colID, ) } if seen.Contains(int(colID)) { return errors.Newf( - "unique without index constraint %q contains duplicate column \"%d\"", c.Name, colID, + "unique without index constraint %q contains duplicate column \"%d\"", c.GetName(), colID, ) } seen.Add(int(colID)) } if c.IsPartial() { - expr, err := parser.ParseExpr(c.Predicate) + expr, err := parser.ParseExpr(c.GetPredicate()) if err != nil { return err } @@ -1188,8 +1190,8 @@ func (desc *wrapper) validateUniqueWithoutIndexConstraints( if !valid { return errors.Newf( "partial unique without index constraint %q refers to unknown columns in predicate: %s", - c.Name, - c.Predicate, + c.GetName(), + c.GetPredicate(), ) } } @@ -1203,9 +1205,7 @@ func (desc *wrapper) validateUniqueWithoutIndexConstraints( // IDs are unique, and the family of the primary key is 0. This does not check // if indexes are unique (i.e. same set of columns, direction, and uniqueness) // as there are practical uses for them. -func (desc *wrapper) validateTableIndexes( - columnsByID map[descpb.ColumnID]catalog.Column, vea catalog.ValidationErrorAccumulator, -) error { +func (desc *wrapper) validateTableIndexes(columnsByID map[descpb.ColumnID]catalog.Column) error { if len(desc.PrimaryIndex.KeyColumnIDs) == 0 { return ErrMissingPrimaryKey } diff --git a/pkg/sql/catalog/tabledesc/validate_test.go b/pkg/sql/catalog/tabledesc/validate_test.go index 8979fe6d1416..d3653c6eaa0a 100644 --- a/pkg/sql/catalog/tabledesc/validate_test.go +++ b/pkg/sql/catalog/tabledesc/validate_test.go @@ -709,9 +709,10 @@ func TestValidateTableDesc(t *testing.T) { ColumnNames: []string{"bar"}, }, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextConstraintID: 2, + NextFamilyID: 1, + NextIndexID: 2, }}, {`invalid index ID 0`, descpb.TableDescriptor{ @@ -725,11 +726,15 @@ func TestValidateTableDesc(t *testing.T) { Families: []descpb.ColumnFamilyDescriptor{ {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, - PrimaryIndex: descpb.IndexDescriptor{ID: 0, Name: "bar", + PrimaryIndex: descpb.IndexDescriptor{ + ID: 0, + Name: "bar", + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{0}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}}, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextFamilyID: 1, + NextConstraintID: 2, }}, {`index "bar" must contain at least 1 column`, descpb.TableDescriptor{ @@ -744,7 +749,11 @@ func TestValidateTableDesc(t *testing.T) { {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, PrimaryIndex: descpb.IndexDescriptor{ - ID: 1, Name: "primary", KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, + ID: 1, + Name: "primary", + ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, + KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, EncodingType: descpb.PrimaryIndexEncoding, Version: descpb.LatestIndexDescriptorVersion, @@ -752,9 +761,10 @@ func TestValidateTableDesc(t *testing.T) { Indexes: []descpb.IndexDescriptor{ {ID: 2, Name: "bar"}, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`mismatched column IDs (1) and names (0)`, descpb.TableDescriptor{ @@ -768,10 +778,16 @@ func TestValidateTableDesc(t *testing.T) { Families: []descpb.ColumnFamilyDescriptor{ {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, - PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", KeyColumnIDs: []descpb.ColumnID{1}}, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + PrimaryIndex: descpb.IndexDescriptor{ + ID: 1, + Name: "bar", + ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, + }, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`mismatched column IDs (1) and names (2)`, descpb.TableDescriptor{ @@ -786,13 +802,14 @@ func TestValidateTableDesc(t *testing.T) { Families: []descpb.ColumnFamilyDescriptor{ {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1, 2}, ColumnNames: []string{"bar", "blah"}}, }, - PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", + PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar", "blah"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`duplicate index name: "bar"`, descpb.TableDescriptor{ @@ -806,7 +823,7 @@ func TestValidateTableDesc(t *testing.T) { Families: []descpb.ColumnFamilyDescriptor{ {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, - PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", + PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, EncodingType: descpb.PrimaryIndexEncoding, @@ -818,9 +835,10 @@ func TestValidateTableDesc(t *testing.T) { KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, }, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`index "blah" duplicate ID of index "bar": 1`, descpb.TableDescriptor{ @@ -834,7 +852,8 @@ func TestValidateTableDesc(t *testing.T) { Families: []descpb.ColumnFamilyDescriptor{ {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, - PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", KeyColumnIDs: []descpb.ColumnID{1}, + PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, EncodingType: descpb.PrimaryIndexEncoding, @@ -846,9 +865,10 @@ func TestValidateTableDesc(t *testing.T) { KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, }, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`index "bar" contains key column "bar" with unknown ID 2`, descpb.TableDescriptor{ @@ -865,13 +885,15 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "bar", + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{2}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`index "bar" key column ID 1 should have name "bar", but found name "blah"`, descpb.TableDescriptor{ @@ -888,13 +910,15 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "bar", + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"blah"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`mismatched column IDs (1) and directions (0)`, descpb.TableDescriptor{ @@ -908,12 +932,14 @@ func TestValidateTableDesc(t *testing.T) { Families: []descpb.ColumnFamilyDescriptor{ {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, - PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", KeyColumnIDs: []descpb.ColumnID{1}, + PrimaryIndex: descpb.IndexDescriptor{ID: 1, Name: "bar", ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"blah"}, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`mismatched STORING column IDs (1) and names (0)`, descpb.TableDescriptor{ @@ -934,15 +960,16 @@ func TestValidateTableDesc(t *testing.T) { }, }, PrimaryIndex: descpb.IndexDescriptor{ - ID: 1, Name: "primary", + ID: 1, Name: "primary", ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, StoreColumnIDs: []descpb.ColumnID{2}, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, }}, {`index "secondary" contains stored column "quux" with unknown ID 123`, descpb.TableDescriptor{ @@ -960,6 +987,7 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "primary", + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, @@ -975,9 +1003,10 @@ func TestValidateTableDesc(t *testing.T) { StoreColumnIDs: []descpb.ColumnID{123}, StoreColumnNames: []string{"quux"}, }}, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`index "secondary" stored column ID 2 should have name "baz", but found name "quux"`, descpb.TableDescriptor{ @@ -999,6 +1028,7 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "primary", + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, @@ -1015,9 +1045,10 @@ func TestValidateTableDesc(t *testing.T) { StoreColumnIDs: []descpb.ColumnID{2}, StoreColumnNames: []string{"quux"}, }}, - NextColumnID: 4, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 4, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`index "secondary" key suffix column ID 123 is invalid`, descpb.TableDescriptor{ @@ -1035,6 +1066,7 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "primary", + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, @@ -1049,9 +1081,10 @@ func TestValidateTableDesc(t *testing.T) { KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, KeySuffixColumnIDs: []descpb.ColumnID{123}, }}, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`index "primary" contains deprecated foreign key representation`, descpb.TableDescriptor{ @@ -1093,7 +1126,8 @@ func TestValidateTableDesc(t *testing.T) { {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1}, ColumnNames: []string{"bar"}}, }, PrimaryIndex: descpb.IndexDescriptor{ - ID: 1, Name: "primary", KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, + ID: 1, Name: "primary", ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, Partitioning: catpb.PartitioningDescriptor{ NumColumns: 1, @@ -1101,9 +1135,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, Version: descpb.LatestIndexDescriptorVersion, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`index "foo_crdb_internal_bar_shard_5_bar_idx" refers to non-existent shard column "does not exist"`, descpb.TableDescriptor{ @@ -1146,9 +1181,10 @@ func TestValidateTableDesc(t *testing.T) { }, }, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`TableID mismatch for unique without index constraint "bar_unique": "1" doesn't match descriptor: "2"`, descpb.TableDescriptor{ @@ -1165,17 +1201,19 @@ func TestValidateTableDesc(t *testing.T) { ColumnNames: []string{"bar"}, }, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextFamilyID: 1, + NextConstraintID: 2, UniqueWithoutIndexConstraints: []descpb.UniqueWithoutIndexConstraint{ { - TableID: 1, - ColumnIDs: []descpb.ColumnID{1}, - Name: "bar_unique", + TableID: 1, + ColumnIDs: []descpb.ColumnID{1}, + Name: "bar_unique", + ConstraintID: 1, }, }, }}, - {`column-id "2" does not exist`, + {`unique without index constraint "bar_unique" contains unknown column "2"`, descpb.TableDescriptor{ ID: 2, ParentID: 1, @@ -1190,13 +1228,15 @@ func TestValidateTableDesc(t *testing.T) { ColumnNames: []string{"bar"}, }, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextFamilyID: 1, + NextConstraintID: 2, UniqueWithoutIndexConstraints: []descpb.UniqueWithoutIndexConstraint{ { - TableID: 2, - ColumnIDs: []descpb.ColumnID{1, 2}, - Name: "bar_unique", + TableID: 2, + ConstraintID: 1, + ColumnIDs: []descpb.ColumnID{1, 2}, + Name: "bar_unique", }, }, }}, @@ -1215,17 +1255,19 @@ func TestValidateTableDesc(t *testing.T) { ColumnNames: []string{"bar"}, }, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextFamilyID: 1, + NextConstraintID: 2, UniqueWithoutIndexConstraints: []descpb.UniqueWithoutIndexConstraint{ { - TableID: 2, - ColumnIDs: []descpb.ColumnID{1, 1}, - Name: "bar_unique", + TableID: 2, + ConstraintID: 1, + ColumnIDs: []descpb.ColumnID{1, 1}, + Name: "bar_unique", }, }, }}, - {`empty unique without index constraint name`, + {`empty constraint name`, descpb.TableDescriptor{ ID: 2, ParentID: 1, @@ -1240,15 +1282,19 @@ func TestValidateTableDesc(t *testing.T) { ColumnNames: []string{"bar"}, }, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextFamilyID: 1, + NextConstraintID: 3, PrimaryIndex: descpb.IndexDescriptor{ - ID: 1, ConstraintID: 1, Name: "primary", KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, - KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}}, + ID: 1, ConstraintID: 1, Name: "primary", + KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"bar"}, + KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, + }, UniqueWithoutIndexConstraints: []descpb.UniqueWithoutIndexConstraint{ { - TableID: 2, - ColumnIDs: []descpb.ColumnID{1}, + TableID: 2, + ConstraintID: 2, + ColumnIDs: []descpb.ColumnID{1}, }, }, }}, @@ -1339,7 +1385,7 @@ func TestValidateTableDesc(t *testing.T) { KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, Version: descpb.LatestIndexDescriptorVersion, EncodingType: descpb.PrimaryIndexEncoding, - ConstraintID: 1, + ConstraintID: 2, }, }, Direction: descpb.DescriptorMutation_ADD, @@ -1373,7 +1419,7 @@ func TestValidateTableDesc(t *testing.T) { NextColumnID: 4, NextFamilyID: 1, NextIndexID: 5, - NextConstraintID: 2, + NextConstraintID: 3, Privileges: catpb.NewBasePrivilegeDescriptor(username.AdminRoleName()), }}, {`index "sec" cannot store virtual column "c3"`, @@ -1390,6 +1436,7 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "primary", + ConstraintID: 1, Unique: true, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, @@ -1416,6 +1463,7 @@ func TestValidateTableDesc(t *testing.T) { Index: &descpb.IndexDescriptor{ ID: 3, Name: "new_primary_key", + ConstraintID: 2, Unique: true, KeyColumnIDs: []descpb.ColumnID{3}, KeyColumnNames: []string{"c3"}, @@ -1452,10 +1500,11 @@ func TestValidateTableDesc(t *testing.T) { State: descpb.DescriptorMutation_DELETE_ONLY, }, }, - NextColumnID: 4, - NextFamilyID: 1, - NextIndexID: 5, - Privileges: catpb.NewBasePrivilegeDescriptor(username.AdminRoleName()), + NextColumnID: 4, + NextFamilyID: 1, + NextIndexID: 5, + NextConstraintID: 3, + Privileges: catpb.NewBasePrivilegeDescriptor(username.AdminRoleName()), }}, {`index "new_sec" cannot store virtual column "c3"`, descpb.TableDescriptor{ @@ -1471,6 +1520,7 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "primary", + ConstraintID: 1, Unique: true, KeyColumnIDs: []descpb.ColumnID{1, 3}, KeyColumnNames: []string{"c1", "c3"}, @@ -1498,6 +1548,7 @@ func TestValidateTableDesc(t *testing.T) { ID: 3, Name: "new_primary_key", Unique: true, + ConstraintID: 2, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, @@ -1533,10 +1584,11 @@ func TestValidateTableDesc(t *testing.T) { State: descpb.DescriptorMutation_DELETE_ONLY, }, }, - NextColumnID: 4, - NextFamilyID: 1, - NextIndexID: 5, - Privileges: catpb.NewBasePrivilegeDescriptor(username.AdminRoleName()), + NextColumnID: 4, + NextFamilyID: 1, + NextIndexID: 5, + NextConstraintID: 3, + Privileges: catpb.NewBasePrivilegeDescriptor(username.AdminRoleName()), }}, {`index "sec" cannot store virtual column "v"`, descpb.TableDescriptor{ @@ -1553,7 +1605,8 @@ func TestValidateTableDesc(t *testing.T) { {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1, 2}, ColumnNames: []string{"c1", "c2"}}, }, PrimaryIndex: descpb.IndexDescriptor{ - ID: 1, Name: "pri", KeyColumnIDs: []descpb.ColumnID{1}, + ID: 1, Name: "pri", ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, EncodingType: descpb.PrimaryIndexEncoding, @@ -1567,9 +1620,10 @@ func TestValidateTableDesc(t *testing.T) { StoreColumnIDs: []descpb.ColumnID{3}, }, }, - NextColumnID: 4, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 4, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`index "sec" has column ID 2 present in: [KeyColumnIDs StoreColumnIDs]`, descpb.TableDescriptor{ @@ -1585,7 +1639,8 @@ func TestValidateTableDesc(t *testing.T) { {ID: 0, Name: "primary", ColumnIDs: []descpb.ColumnID{1, 2}, ColumnNames: []string{"c1", "c2"}}, }, PrimaryIndex: descpb.IndexDescriptor{ - ID: 1, Name: "pri", KeyColumnIDs: []descpb.ColumnID{1}, + ID: 1, Name: "pri", ConstraintID: 1, + KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, EncodingType: descpb.PrimaryIndexEncoding, @@ -1600,9 +1655,10 @@ func TestValidateTableDesc(t *testing.T) { Version: descpb.LatestIndexDescriptorVersion, }, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 3, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 3, + NextConstraintID: 2, }}, {`computed column "bar" cannot also have an ON UPDATE expression`, descpb.TableDescriptor{ @@ -1862,6 +1918,7 @@ func TestValidateTableDesc(t *testing.T) { PrimaryIndex: descpb.IndexDescriptor{ ID: 1, Name: "primary", + ConstraintID: 1, Unique: true, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, @@ -1880,9 +1937,10 @@ func TestValidateTableDesc(t *testing.T) { UseDeletePreservingEncoding: true, }, }, - NextColumnID: 3, - NextIndexID: 3, - NextFamilyID: 1, + NextColumnID: 3, + NextIndexID: 3, + NextFamilyID: 1, + NextConstraintID: 2, }}, {`public index "primary" is using the delete preserving encoding`, descpb.TableDescriptor{ @@ -1901,6 +1959,7 @@ func TestValidateTableDesc(t *testing.T) { ID: 1, Name: "primary", Unique: true, + ConstraintID: 1, KeyColumnIDs: []descpb.ColumnID{1}, KeyColumnNames: []string{"c1"}, KeyColumnDirections: []catpb.IndexColumn_Direction{catpb.IndexColumn_ASC}, @@ -1908,9 +1967,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, UseDeletePreservingEncoding: true, }, - NextColumnID: 3, - NextIndexID: 2, - NextFamilyID: 1, + NextColumnID: 3, + NextIndexID: 2, + NextFamilyID: 1, + NextConstraintID: 2, }, }, {`column ID 123 found in depended-on-by references, no such column in this relation`, @@ -2065,9 +2125,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ ExpirationExpr: catpb.Expression("missing_col"), }, @@ -2095,9 +2156,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ SelectBatchSize: 5, }, @@ -2125,9 +2187,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 2, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 2, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ DurationExpr: catpb.Expression("INTERVAL '2 minutes'"), }, @@ -2161,9 +2224,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ DurationExpr: catpb.Expression("INTERVAL '2 minutes'"), }, @@ -2197,9 +2261,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ DurationExpr: catpb.Expression("INTERVAL '2 minutes'"), }, @@ -2234,9 +2299,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ DurationExpr: catpb.Expression("INTERVAL '2 minutes'"), SelectBatchSize: -2, @@ -2272,9 +2338,10 @@ func TestValidateTableDesc(t *testing.T) { EncodingType: descpb.PrimaryIndexEncoding, ConstraintID: 1, }, - NextColumnID: 3, - NextFamilyID: 1, - NextIndexID: 2, + NextColumnID: 3, + NextFamilyID: 1, + NextIndexID: 2, + NextConstraintID: 2, RowLevelTTL: &catpb.RowLevelTTL{ DurationExpr: catpb.Expression("INTERVAL '2 minutes'"), }, @@ -3010,7 +3077,7 @@ func TestValidateConstraintID(t *testing.T) { err string desc descpb.TableDescriptor }{ - {`constraint ID was missing for constraint: PRIMARY KEY with name \"primary\"`, + {`constraint ID was missing for constraint \"primary\"`, descpb.TableDescriptor{ ID: 2, ParentID: 1, @@ -3033,7 +3100,7 @@ func TestValidateConstraintID(t *testing.T) { privilege.List{}, username.RootUserName()), }}, - {`constraint ID was missing for constraint: UNIQUE with name \"secondary\"`, + {`constraint ID was missing for constraint \"secondary\"`, descpb.TableDescriptor{ ID: 2, ParentID: 1, @@ -3055,15 +3122,16 @@ func TestValidateConstraintID(t *testing.T) { Unique: true, }, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextFamilyID: 1, + NextConstraintID: 2, Privileges: catpb.NewPrivilegeDescriptor( username.PublicRoleName(), privilege.SchemaPrivileges, privilege.List{}, username.RootUserName()), }}, - {`constraint ID was missing for constraint: UNIQUE with name \"bad\"`, + {`constraint ID was missing for constraint \"bad\"`, descpb.TableDescriptor{ ID: 2, ParentID: 1, @@ -3081,15 +3149,16 @@ func TestValidateConstraintID(t *testing.T) { UniqueWithoutIndexConstraints: []descpb.UniqueWithoutIndexConstraint{ {Name: "bad"}, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextConstraintID: 2, + NextFamilyID: 1, Privileges: catpb.NewPrivilegeDescriptor( username.PublicRoleName(), privilege.SchemaPrivileges, privilege.List{}, username.RootUserName()), }}, - {`constraint ID was missing for constraint: CHECK with name \"bad\"`, + {`constraint ID was missing for constraint \"bad\"`, descpb.TableDescriptor{ ID: 2, ParentID: 1, @@ -3107,8 +3176,9 @@ func TestValidateConstraintID(t *testing.T) { Checks: []*descpb.TableDescriptor_CheckConstraint{ {Name: "bad"}, }, - NextColumnID: 2, - NextFamilyID: 1, + NextColumnID: 2, + NextConstraintID: 2, + NextFamilyID: 1, Privileges: catpb.NewPrivilegeDescriptor( username.PublicRoleName(), privilege.SchemaPrivileges, diff --git a/pkg/sql/check.go b/pkg/sql/check.go index ba48e5613c6d..d198dff21131 100644 --- a/pkg/sql/check.go +++ b/pkg/sql/check.go @@ -467,14 +467,14 @@ func (p *planner) RevalidateUniqueConstraint( } // Check UNIQUE WITHOUT INDEX constraints. - for _, uc := range tableDesc.GetUniqueWithoutIndexConstraints() { - if uc.Name == constraintName { + for _, uc := range tableDesc.EnforcedUniqueConstraintsWithoutIndex() { + if uc.GetName() == constraintName { return validateUniqueConstraint( ctx, tableDesc, - uc.Name, - uc.ColumnIDs, - uc.Predicate, + uc.GetName(), + uc.CollectKeyColumnIDs().Ordered(), + uc.GetPredicate(), p.ExecCfg().InternalExecutor, p.Txn(), p.User(), @@ -500,31 +500,8 @@ func (p *planner) IsConstraintActive( if err != nil { return false, err } - constraints, err := tableDesc.GetConstraintInfo() - if err != nil { - return false, err - } - cnst := constraints[constraintName] - validated := !cnst.Unvalidated - // For foreign keys we only care what the outbound foreign key state, - // shows for the constraint. Since we need to know if this constraint - // will be active on inserts. - if cnst.Kind == descpb.ConstraintTypeFK { - validated = false - err := tableDesc.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - if fk.Name != constraintName { - return nil - } - validated = fk.Validity == descpb.ConstraintValidity_Validated || - fk.Validity == descpb.ConstraintValidity_Validating || - fk.Validity == descpb.ConstraintValidity_Unvalidated - return nil - }) - if err != nil { - return false, err - } - } - return validated, nil + constraint, _ := tableDesc.FindConstraintWithName(constraintName) + return constraint != nil && constraint.IsEnforced(), nil } // HasVirtualUniqueConstraints returns true if the table has one or more @@ -535,8 +512,8 @@ func HasVirtualUniqueConstraints(tableDesc catalog.TableDescriptor) bool { return true } } - for _, uc := range tableDesc.GetUniqueWithoutIndexConstraints() { - if uc.Validity == descpb.ConstraintValidity_Validated { + for _, uc := range tableDesc.EnforcedUniqueConstraintsWithoutIndex() { + if uc.IsConstraintValidated() { return true } } @@ -579,14 +556,14 @@ func RevalidateUniqueConstraintsInTable( } // Check UNIQUE WITHOUT INDEX constraints. - for _, uc := range tableDesc.GetUniqueWithoutIndexConstraints() { - if uc.Validity == descpb.ConstraintValidity_Validated { + for _, uc := range tableDesc.EnforcedUniqueConstraintsWithoutIndex() { + if uc.IsConstraintValidated() { if err := validateUniqueConstraint( ctx, tableDesc, - uc.Name, - uc.ColumnIDs, - uc.Predicate, + uc.GetName(), + uc.CollectKeyColumnIDs().Ordered(), + uc.GetPredicate(), ie, txn, user, @@ -860,7 +837,7 @@ func checkMutationInput( "mismatched check constraint columns: expected %d, got %d", checkOrds.Len(), len(checkVals)) } - checks := tabDesc.ActiveChecks() + checks := tabDesc.EnforcedCheckConstraints() colIdx := 0 for i := range checks { if !checkOrds.Contains(i) { @@ -873,16 +850,16 @@ func checkMutationInput( // Failed to satisfy CHECK constraint, so unwrap the serialized // check expression to display to the user. expr, err := schemaexpr.FormatExprForDisplay( - ctx, tabDesc, checks[i].Expr, semaCtx, sessionData, tree.FmtParsable, + ctx, tabDesc, checks[i].GetExpr(), semaCtx, sessionData, tree.FmtParsable, ) if err != nil { // If we ran into an error trying to read the check constraint, wrap it // and return. - return pgerror.WithConstraintName(errors.Wrapf(err, "failed to satisfy CHECK constraint (%s)", checks[i].Expr), checks[i].Name) + return pgerror.WithConstraintName(errors.Wrapf(err, "failed to satisfy CHECK constraint (%s)", checks[i].GetExpr()), checks[i].GetName()) } return pgerror.WithConstraintName(pgerror.Newf( pgcode.CheckViolation, "failed to satisfy CHECK constraint (%s)", expr, - ), checks[i].Name) + ), checks[i].GetName()) } colIdx++ } diff --git a/pkg/sql/comment_on_constraint.go b/pkg/sql/comment_on_constraint.go index 88fd6d23d24e..e35ec6694a7e 100644 --- a/pkg/sql/comment_on_constraint.go +++ b/pkg/sql/comment_on_constraint.go @@ -67,14 +67,9 @@ func (p *planner) CommentOnConstraint( } func (n *commentOnConstraintNode) startExec(params runParams) error { - info, err := n.tableDesc.GetConstraintInfo() - if err != nil { - return err - } - constraintName := string(n.n.Constraint) - constraint, ok := info[constraintName] - if !ok { + constraint, _ := n.tableDesc.FindConstraintWithName(constraintName) + if constraint == nil { return pgerror.Newf(pgcode.UndefinedObject, "constraint %q of relation %q does not exist", constraintName, n.tableDesc.GetName()) } @@ -83,7 +78,7 @@ func (n *commentOnConstraintNode) startExec(params runParams) error { if n.n.Comment != nil { err := n.metadataUpdater.UpsertConstraintComment( n.tableDesc.GetID(), - constraint.ConstraintID, + constraint.GetConstraintID(), *n.n.Comment, ) if err != nil { @@ -92,7 +87,7 @@ func (n *commentOnConstraintNode) startExec(params runParams) error { } else { err := n.metadataUpdater.DeleteConstraintComment( n.tableDesc.GetID(), - constraint.ConstraintID, + constraint.GetConstraintID(), ) if err != nil { return err diff --git a/pkg/sql/crdb_internal.go b/pkg/sql/crdb_internal.go index 930509a9f62e..be249339f1a4 100644 --- a/pkg/sql/crdb_internal.go +++ b/pkg/sql/crdb_internal.go @@ -2938,12 +2938,12 @@ func showAlterStatement( alterStmts *tree.DArray, validateStmts *tree.DArray, ) error { - return table.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { + for _, fk := range table.OutboundForeignKeys() { f := tree.NewFmtCtx(tree.FmtSimple) f.WriteString("ALTER TABLE ") f.FormatNode(tn) f.WriteString(" ADD CONSTRAINT ") - f.FormatNameP(&fk.Name) + f.FormatName(fk.GetName()) f.WriteByte(' ') // Passing in EmptySearchPath causes the schema name to show up in the // constraint definition, which we need for `cockroach dump` output to be @@ -2952,7 +2952,7 @@ func showAlterStatement( &f.Buffer, contextName, table, - fk, + fk.ForeignKeyDesc(), lCtx, sessiondata.EmptySearchPath, ); err != nil { @@ -2966,10 +2966,13 @@ func showAlterStatement( f.WriteString("ALTER TABLE ") f.FormatNode(tn) f.WriteString(" VALIDATE CONSTRAINT ") - f.FormatNameP(&fk.Name) + f.FormatName(fk.GetName()) - return validateStmts.Append(tree.NewDString(f.CloseAndGetString())) - }) + if err := validateStmts.Append(tree.NewDString(f.CloseAndGetString())); err != nil { + return err + } + } + return nil } // crdbInternalTableColumnsTable exposes the column descriptors. @@ -3285,31 +3288,31 @@ CREATE TABLE crdb_internal.backward_dependencies ( tableID := tree.NewDInt(tree.DInt(table.GetID())) tableName := tree.NewDString(table.GetName()) - if err := table.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - refTbl, err := tableLookup.getTableByID(fk.ReferencedTableID) + for _, fk := range table.OutboundForeignKeys() { + refTbl, err := tableLookup.getTableByID(fk.GetReferencedTableID()) if err != nil { return err } - refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint(refTbl, fk.ReferencedColumnIDs) + refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint(refTbl, fk) if err != nil { return err } var refIdxID descpb.IndexID - if refIdx, ok := refConstraint.(*descpb.IndexDescriptor); ok { - refIdxID = refIdx.ID + if refIdx := refConstraint.AsUniqueWithIndex(); refIdx != nil { + refIdxID = refIdx.GetID() } - return addRow( + if err := addRow( tableID, tableName, tree.DNull, tree.DNull, - tree.NewDInt(tree.DInt(fk.ReferencedTableID)), + tree.NewDInt(tree.DInt(fk.GetReferencedTableID())), fkDep, tree.NewDInt(tree.DInt(refIdxID)), - tree.NewDString(fk.Name), + tree.NewDString(fk.GetName()), tree.DNull, - ) - }); err != nil { - return err + ); err != nil { + return err + } } // Record the view dependencies. @@ -3417,19 +3420,18 @@ CREATE TABLE crdb_internal.forward_dependencies ( func(db catalog.DatabaseDescriptor, _ catalog.SchemaDescriptor, table catalog.TableDescriptor) error { tableID := tree.NewDInt(tree.DInt(table.GetID())) tableName := tree.NewDString(table.GetName()) - - if err := table.ForeachInboundFK(func(fk *descpb.ForeignKeyConstraint) error { - return addRow( + for _, fk := range table.InboundForeignKeys() { + if err := addRow( tableID, tableName, tree.DNull, - tree.NewDInt(tree.DInt(fk.OriginTableID)), + tree.NewDInt(tree.DInt(fk.GetOriginTableID())), fkDep, tree.DNull, tree.DNull, tree.DNull, - ) - }); err != nil { - return err + ); err != nil { + return err + } } reportDependedOnBy := func( @@ -5290,33 +5292,28 @@ CREATE TABLE crdb_internal.cross_db_references ( // references to a different database. if table.IsTable() { objectDatabaseName := lookupFn.getDatabaseName(table) - err := table.ForeachOutboundFK( - func(fk *descpb.ForeignKeyConstraint) error { - referencedTable, err := lookupFn.getTableByID(fk.ReferencedTableID) + for _, fk := range table.OutboundForeignKeys() { + referencedTable, err := lookupFn.getTableByID(fk.GetReferencedTableID()) + if err != nil { + return err + } + if referencedTable.GetParentID() != table.GetParentID() { + refSchemaName, err := lookupFn.getSchemaNameByID(referencedTable.GetParentSchemaID()) if err != nil { return err } - if referencedTable.GetParentID() != table.GetParentID() { - refSchemaName, err := lookupFn.getSchemaNameByID(referencedTable.GetParentSchemaID()) - if err != nil { - return err - } - refDatabaseName := lookupFn.getDatabaseName(referencedTable) + refDatabaseName := lookupFn.getDatabaseName(referencedTable) - if err := addRow(tree.NewDString(objectDatabaseName), - tree.NewDString(sc.GetName()), - tree.NewDString(table.GetName()), - tree.NewDString(refDatabaseName), - tree.NewDString(refSchemaName), - tree.NewDString(referencedTable.GetName()), - tree.NewDString("table foreign key reference")); err != nil { - return err - } + if err := addRow(tree.NewDString(objectDatabaseName), + tree.NewDString(sc.GetName()), + tree.NewDString(table.GetName()), + tree.NewDString(refDatabaseName), + tree.NewDString(refSchemaName), + tree.NewDString(referencedTable.GetName()), + tree.NewDString("table foreign key reference")); err != nil { + return err } - return nil - }) - if err != nil { - return err + } } // Check for sequence dependencies diff --git a/pkg/sql/create_index.go b/pkg/sql/create_index.go index 520f8c37f630..2415e83a4127 100644 --- a/pkg/sql/create_index.go +++ b/pkg/sql/create_index.go @@ -118,14 +118,9 @@ func (p *planner) maybeSetupConstraintForShard( return err } - curConstraintInfos, err := tableDesc.GetConstraintInfo() - if err != nil { - return err - } - // Avoid creating duplicate check constraints. - for _, info := range curConstraintInfos { - if info.CheckConstraint != nil && info.CheckConstraint.Expr == ckDesc.Expr { + for _, ck := range tableDesc.CheckConstraints() { + if ck.GetExpr() == ckDesc.Expr && ck.IsConstraintValidated() { return nil } } diff --git a/pkg/sql/create_table.go b/pkg/sql/create_table.go index e73f64966310..df3e49e4c14f 100644 --- a/pkg/sql/create_table.go +++ b/pkg/sql/create_table.go @@ -766,20 +766,16 @@ func ResolveUniqueWithoutIndexConstraint( } // Verify we are not writing a constraint over the same name. - constraintInfo, err := tbl.GetConstraintInfo() - if err != nil { - return err - } if constraintName == "" { constraintName = tabledesc.GenerateUniqueName( fmt.Sprintf("unique_%s", strings.Join(colNames, "_")), func(p string) bool { - _, ok := constraintInfo[p] - return ok + c, _ := tbl.FindConstraintWithName(p) + return c != nil }, ) } else { - if _, ok := constraintInfo[constraintName]; ok { + if c, _ := tbl.FindConstraintWithName(constraintName); c != nil { return pgerror.Newf(pgcode.DuplicateObject, "duplicate constraint name: %q", constraintName) } } @@ -976,21 +972,17 @@ func ResolveFK( // or else we can hit other checks that break things with // undesired error codes, e.g. #42858. // It may be removable after #37255 is complete. - constraintInfo, err := tbl.GetConstraintInfo() - if err != nil { - return err - } constraintName := string(d.Name) if constraintName == "" { constraintName = tabledesc.GenerateUniqueName( tabledesc.ForeignKeyConstraintName(tbl.GetName(), d.FromCols.ToStrings()), func(p string) bool { - _, ok := constraintInfo[p] - return ok + c, _ := tbl.FindConstraintWithName(p) + return c != nil }, ) } else { - if _, ok := constraintInfo[constraintName]; ok { + if c, _ := tbl.FindConstraintWithName(constraintName); c != nil { return pgerror.Newf(pgcode.DuplicateObject, "duplicate constraint name: %q", constraintName) } } @@ -1034,12 +1026,6 @@ func ResolveFK( } } - // Ensure that there is a unique constraint on the referenced side to use. - _, err = tabledesc.FindFKReferencedUniqueConstraint(target, targetColIDs) - if err != nil { - return err - } - var validity descpb.ConstraintValidity if ts != NewTable { if validationBehavior == tree.ValidationSkip { @@ -1069,7 +1055,13 @@ func ResolveFK( tbl.AddForeignKeyMutation(&ref, descpb.DescriptorMutation_ADD) } - return nil + c, err := tbl.FindConstraintWithID(ref.ConstraintID) + if err != nil { + return errors.HandleAsAssertionFailure(err) + } + // Ensure that there is a unique constraint on the referenced side to use. + _, err = tabledesc.FindFKReferencedUniqueConstraint(target, c.(catalog.ForeignKeyConstraint)) + return err } // CreatePartitioning returns a set of implicit columns and a new partitioning diff --git a/pkg/sql/drop_index.go b/pkg/sql/drop_index.go index fc7a1e338441..dbcb671a30c4 100644 --- a/pkg/sql/drop_index.go +++ b/pkg/sql/drop_index.go @@ -247,16 +247,16 @@ func (n *dropIndexNode) dropShardColumnAndConstraint( tableDesc *tabledesc.Mutable, shardCol catalog.Column, ) error { validChecks := tableDesc.Checks[:0] - for _, check := range tableDesc.AllActiveAndInactiveChecks() { - if used, err := tableDesc.CheckConstraintUsesColumn(check, shardCol.GetID()); err != nil { + for _, check := range tableDesc.CheckConstraints() { + if used, err := tableDesc.CheckConstraintUsesColumn(check.CheckDesc(), shardCol.GetID()); err != nil { return err } else if used { - if check.Validity == descpb.ConstraintValidity_Validating { + if check.GetConstraintValidity() == descpb.ConstraintValidity_Validating { return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, - "referencing constraint %q in the middle of being added, try again later", check.Name) + "referencing constraint %q in the middle of being added, try again later", check.GetName()) } } else { - validChecks = append(validChecks, check) + validChecks = append(validChecks, check.CheckDesc()) } } @@ -390,57 +390,40 @@ func (p *planner) dropIndexByName( // a mixed version cluster, but we have to remove all reads of the legacy // explicit index fields. - // Construct a list of all the remaining indexes, so that we can see if there - // is another index that could replace the one we are deleting for a given - // foreign key constraint. - remainingIndexes := make([]catalog.Index, 1, len(tableDesc.ActiveIndexes())) - remainingIndexes[0] = tableDesc.GetPrimaryIndex() - for _, index := range tableDesc.PublicNonPrimaryIndexes() { - if index.GetID() != idx.GetID() { - remainingIndexes = append(remainingIndexes, index) + // Check for foreign key mutations referencing this index. + activeIndexes := tableDesc.ActiveIndexes() + for _, fk := range tableDesc.OutboundForeignKeys() { + if !fk.IsMutation() || !idx.IsValidOriginIndex(fk) { + continue } - } - - // indexHasReplacementCandidate runs isValidIndex on each index in remainingIndexes and returns - // true if at least one index satisfies isValidIndex. - indexHasReplacementCandidate := func(isValidIndex func(index catalog.Index) bool) bool { - foundReplacement := false - for _, index := range remainingIndexes { - if isValidIndex(index) { + // If the index being deleted could be used as a index for this outbound + // foreign key mutation, then make sure that we have another index that + // could be used for this mutation. + var foundReplacement bool + for _, index := range activeIndexes { + if index.GetID() != idx.GetID() && index.IsValidOriginIndex(fk) { foundReplacement = true break } } - return foundReplacement - } - - // Check for foreign key mutations referencing this index. - for _, m := range tableDesc.Mutations { - if c := m.GetConstraint(); c != nil && - c.ConstraintType == descpb.ConstraintToUpdate_FOREIGN_KEY && - // If the index being deleted could be used as a index for this outbound - // foreign key mutation, then make sure that we have another index that - // could be used for this mutation. - idx.IsValidOriginIndex(c.ForeignKey.OriginColumnIDs) && - !indexHasReplacementCandidate(func(idx catalog.Index) bool { - return idx.IsValidOriginIndex(c.ForeignKey.OriginColumnIDs) - }) { + if !foundReplacement { return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, - "referencing constraint %q in the middle of being added, try again later", c.ForeignKey.Name) + "referencing constraint %q in the middle of being added, try again later", + fk.GetName(), + ) } } // If this index is used on the referencing side of any FK constraints, try // to remove the references or find an alternate index that will suffice. - candidateConstraints := make([]descpb.UniqueConstraint, len(remainingIndexes)) - for i := range remainingIndexes { - // We can't copy directly because of the interface conversion. - candidateConstraints[i] = remainingIndexes[i] - } - if err := p.tryRemoveFKBackReferences( - ctx, tableDesc, idx, behavior, candidateConstraints, - ); err != nil { - return err + if uwi := idx.AsUniqueWithIndex(); uwi != nil { + + const withSearchForReplacement = true + if err := p.tryRemoveFKBackReferences( + ctx, tableDesc, uwi, behavior, withSearchForReplacement, + ); err != nil { + return err + } } var droppedViews []string diff --git a/pkg/sql/drop_table.go b/pkg/sql/drop_table.go index dc50fd1c497d..bd4fa82d72ff 100644 --- a/pkg/sql/drop_table.go +++ b/pkg/sql/drop_table.go @@ -75,10 +75,9 @@ func (p *planner) DropTable(ctx context.Context, n *tree.DropTable) (planNode, e for _, toDel := range td { droppedDesc := toDel.desc - for i := range droppedDesc.InboundFKs { - ref := &droppedDesc.InboundFKs[i] - if _, ok := td[ref.OriginTableID]; !ok { - if err := p.canRemoveFKBackreference(ctx, droppedDesc.Name, ref, n.DropBehavior); err != nil { + for _, fk := range droppedDesc.InboundForeignKeys() { + if _, ok := td[fk.GetOriginTableID()]; !ok { + if err := p.canRemoveFKBackreference(ctx, droppedDesc.Name, fk, n.DropBehavior); err != nil { return nil, err } } @@ -203,9 +202,9 @@ func (p *planner) canDropTable( // canRemoveFKBackReference returns an error if the input backreference isn't // allowed to be removed. func (p *planner) canRemoveFKBackreference( - ctx context.Context, from string, ref *descpb.ForeignKeyConstraint, behavior tree.DropBehavior, + ctx context.Context, from string, ref catalog.ForeignKeyConstraint, behavior tree.DropBehavior, ) error { - table, err := p.Descriptors().GetMutableTableVersionByID(ctx, ref.OriginTableID, p.txn) + table, err := p.Descriptors().GetMutableTableVersionByID(ctx, ref.GetOriginTableID(), p.txn) if err != nil { return err } @@ -245,9 +244,7 @@ func (p *planner) dropTableImpl( // Remove foreign key forward references from tables that have foreign keys // to this table. // Copy out the set of inbound fks as it may be overwritten in the loop. - inboundFKs := append([]descpb.ForeignKeyConstraint(nil), tableDesc.InboundFKs...) - for i := range inboundFKs { - ref := &tableDesc.InboundFKs[i] + for _, ref := range tableDesc.InboundForeignKeys() { if err := p.removeFKForBackReference(ctx, tableDesc, ref); err != nil { return droppedViews, err } @@ -449,16 +446,16 @@ func (p *planner) markTableMutationJobsSuccessful( } func (p *planner) removeFKForBackReference( - ctx context.Context, tableDesc *tabledesc.Mutable, ref *descpb.ForeignKeyConstraint, + ctx context.Context, tableDesc *tabledesc.Mutable, ref catalog.ForeignKeyConstraint, ) error { var originTableDesc *tabledesc.Mutable // We don't want to lookup/edit a second copy of the same table. - if tableDesc.ID == ref.OriginTableID { + if tableDesc.ID == ref.GetOriginTableID() { originTableDesc = tableDesc } else { - lookup, err := p.Descriptors().GetMutableTableVersionByID(ctx, ref.OriginTableID, p.txn) + lookup, err := p.Descriptors().GetMutableTableVersionByID(ctx, ref.GetOriginTableID(), p.txn) if err != nil { - return errors.Wrapf(err, "error resolving origin table ID %d", ref.OriginTableID) + return errors.Wrapf(err, "error resolving origin table ID %d", ref.GetOriginTableID()) } originTableDesc = lookup } @@ -475,7 +472,8 @@ func (p *planner) removeFKForBackReference( if err != nil { return err } - jobDesc := fmt.Sprintf("updating table %q after removing constraint %q from table %q", originTableDesc.GetName(), ref.Name, name.FQString()) + jobDesc := fmt.Sprintf("updating table %q after removing constraint %q from table %q", + originTableDesc.GetName(), ref.GetName(), name.FQString()) return p.writeSchemaChange(ctx, originTableDesc, descpb.InvalidMutationID, jobDesc) } @@ -484,12 +482,12 @@ func (p *planner) removeFKForBackReference( // backreference, which is a member of the supplied referencedTableDesc. func removeFKForBackReferenceFromTable( originTableDesc *tabledesc.Mutable, - backref *descpb.ForeignKeyConstraint, + backref catalog.ForeignKeyConstraint, referencedTableDesc catalog.TableDescriptor, ) error { matchIdx := -1 for i, fk := range originTableDesc.OutboundFKs { - if fk.ReferencedTableID == referencedTableDesc.GetID() && fk.Name == backref.Name { + if fk.ReferencedTableID == referencedTableDesc.GetID() && fk.Name == backref.GetName() { // We found a match! We want to delete it from the list now. matchIdx = i break diff --git a/pkg/sql/importer/import_job.go b/pkg/sql/importer/import_job.go index 4c382a95985f..b05451ee0e13 100644 --- a/pkg/sql/importer/import_job.go +++ b/pkg/sql/importer/import_job.go @@ -1008,11 +1008,11 @@ func (r *importResumer) publishTables( } // Set CHECK constraints to unvalidated before publishing the table imported into. - for _, c := range newTableDesc.AllActiveAndInactiveChecks() { + for _, c := range newTableDesc.CheckConstraints() { // We only "unvalidate" constraints that are not hash-sharded column // check constraints. - if !c.FromHashShardedColumn { - c.Validity = descpb.ConstraintValidity_Unvalidated + if !c.IsHashShardingConstraint() { + c.CheckDesc().Validity = descpb.ConstraintValidity_Unvalidated } } } diff --git a/pkg/sql/information_schema.go b/pkg/sql/information_schema.go index 321a0069d50d..86bb77b1885b 100644 --- a/pkg/sql/information_schema.go +++ b/pkg/sql/information_schema.go @@ -307,26 +307,17 @@ https://www.postgresql.org/docs/9.5/infoschema-check-constraints.html`, table catalog.TableDescriptor, tableLookup tableLookupFn, ) error { - conInfo, err := table.GetConstraintInfoWithLookup(tableLookup.getTableByID) - if err != nil { - return err - } dbNameStr := tree.NewDString(db.GetName()) scNameStr := tree.NewDString(sc.GetName()) - for conName, con := range conInfo { - // Only Check constraints are included. - if con.Kind != descpb.ConstraintTypeCheck { - continue - } - conNameStr := tree.NewDString(conName) + for _, ck := range table.EnforcedCheckConstraints() { // Like with pg_catalog.pg_constraint, Postgres wraps the check // constraint expression in two pairs of parentheses. - chkExprStr := tree.NewDString(fmt.Sprintf("((%s))", con.Details)) + chkExprStr := tree.NewDString(fmt.Sprintf("((%s))", ck.GetExpr())) if err := addRow( - dbNameStr, // constraint_catalog - scNameStr, // constraint_schema - conNameStr, // constraint_name - chkExprStr, // check_clause + dbNameStr, // constraint_catalog + scNameStr, // constraint_schema + tree.NewDString(ck.GetName()), // constraint_name + chkExprStr, // check_clause ); err != nil { return err } @@ -759,37 +750,44 @@ https://www.postgresql.org/docs/9.5/infoschema-constraint-column-usage.html`, table catalog.TableDescriptor, tableLookup tableLookupFn, ) error { - conInfo, err := table.GetConstraintInfoWithLookup(tableLookup.getTableByID) - if err != nil { - return err - } - scNameStr := tree.NewDString(sc.GetName()) dbNameStr := tree.NewDString(db.GetName()) - - for conName, con := range conInfo { - conTable := table - conCols := con.Columns - conNameStr := tree.NewDString(conName) - if con.Kind == descpb.ConstraintTypeFK { - // For foreign key constraint, constraint_column_usage - // identifies the table/columns that the foreign key - // references. - conTable = tabledesc.NewBuilder(con.ReferencedTable).BuildImmutableTable() - conCols, err = conTable.NamesForColumnIDs(con.FK.ReferencedColumnIDs) + scNameStr := tree.NewDString(sc.GetName()) + for _, c := range table.AllConstraints() { + conNameStr := tree.NewDString(c.GetName()) + refSchema := sc + refTable := table + var cols []catalog.Column + if ck := c.AsCheck(); ck != nil { + cols = table.CheckConstraintColumns(ck) + } else if fk := c.AsForeignKey(); fk != nil { + var err error + refTable, err = tableLookup.getTableByID(fk.GetReferencedTableID()) if err != nil { - return err + return errors.NewAssertionErrorWithWrappedErrf(err, + "error resolving table %d referenced in foreign key %q in table %q", + fk.GetReferencedTableID(), fk.GetName(), table.GetName()) + } + refSchema, err = tableLookup.getSchemaByID(refTable.GetParentSchemaID()) + if err != nil { + return errors.NewAssertionErrorWithWrappedErrf(err, + "error resolving schema %d referenced in foreign key %q in table %q", + refTable.GetParentSchemaID(), fk.GetName(), table.GetName()) } + cols = refTable.ForeignKeyReferencedColumns(fk) + } else if uwi := c.AsUniqueWithIndex(); uwi != nil { + cols = table.IndexKeyColumns(uwi) + } else if uwoi := c.AsUniqueWithoutIndex(); uwoi != nil { + cols = table.UniqueWithoutIndexColumns(uwoi) } - tableNameStr := tree.NewDString(conTable.GetName()) - for _, col := range conCols { + for _, col := range cols { if err := addRow( - dbNameStr, // table_catalog - scNameStr, // table_schema - tableNameStr, // table_name - tree.NewDString(col), // column_name - dbNameStr, // constraint_catalog - scNameStr, // constraint_schema - conNameStr, // constraint_name + dbNameStr, // table_catalog + tree.NewDString(refSchema.GetName()), // table_schema + tree.NewDString(refTable.GetName()), // table_name + tree.NewDString(col.GetName()), // column_name + dbNameStr, // constraint_catalog + scNameStr, // constraint_schema + conNameStr, // constraint_name ); err != nil { return err } @@ -813,41 +811,36 @@ https://www.postgresql.org/docs/9.5/infoschema-key-column-usage.html`, table catalog.TableDescriptor, tableLookup tableLookupFn, ) error { - conInfo, err := table.GetConstraintInfoWithLookup(tableLookup.getTableByID) - if err != nil { - return err - } dbNameStr := tree.NewDString(db.GetName()) scNameStr := tree.NewDString(sc.GetName()) tbNameStr := tree.NewDString(table.GetName()) - for conName, con := range conInfo { + for _, c := range table.AllConstraints() { + cstNameStr := tree.NewDString(c.GetName()) + var cols []catalog.Column // Only Primary Key, Foreign Key, and Unique constraints are included. - switch con.Kind { - case descpb.ConstraintTypePK: - case descpb.ConstraintTypeFK: - case descpb.ConstraintTypeUnique: - default: - continue + if fk := c.AsForeignKey(); fk != nil { + cols = table.ForeignKeyOriginColumns(fk) + } else if uwi := c.AsUniqueWithIndex(); uwi != nil { + cols = table.IndexKeyColumns(uwi) + } else if uwoi := c.AsUniqueWithoutIndex(); uwoi != nil { + cols = table.UniqueWithoutIndexColumns(uwoi) } - - cstNameStr := tree.NewDString(conName) - - for pos, col := range con.Columns { + for pos, col := range cols { ordinalPos := tree.NewDInt(tree.DInt(pos + 1)) uniquePos := tree.DNull - if con.Kind == descpb.ConstraintTypeFK { + if c.AsForeignKey() != nil { uniquePos = ordinalPos } if err := addRow( - dbNameStr, // constraint_catalog - scNameStr, // constraint_schema - cstNameStr, // constraint_name - dbNameStr, // table_catalog - scNameStr, // table_schema - tbNameStr, // table_name - tree.NewDString(col), // column_name - ordinalPos, // ordinal_position, 1-indexed - uniquePos, // position_in_unique_constraint + dbNameStr, // constraint_catalog + scNameStr, // constraint_schema + cstNameStr, // constraint_name + dbNameStr, // table_catalog + scNameStr, // table_schema + tbNameStr, // table_name + tree.NewDString(col.GetName()), // column_name + ordinalPos, // ordinal_position, 1-indexed + uniquePos, // position_in_unique_constraint ); err != nil { return err } @@ -920,35 +913,36 @@ https://www.postgresql.org/docs/9.5/infoschema-referential-constraints.html`, dbNameStr := tree.NewDString(db.GetName()) scNameStr := tree.NewDString(sc.GetName()) tbNameStr := tree.NewDString(table.GetName()) - return table.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - refTable, err := tableLookup.getTableByID(fk.ReferencedTableID) + for _, fk := range table.OutboundForeignKeys() { + refTable, err := tableLookup.getTableByID(fk.GetReferencedTableID()) if err != nil { return err } var matchType = tree.DNull - if r, ok := matchOptionMap[fk.Match]; ok { + if r, ok := matchOptionMap[fk.Match()]; ok { matchType = r } - refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint( - refTable, fk.ReferencedColumnIDs, - ) + refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint(refTable, fk) if err != nil { return err } - return addRow( + if err := addRow( dbNameStr, // constraint_catalog scNameStr, // constraint_schema - tree.NewDString(fk.Name), // constraint_name + tree.NewDString(fk.GetName()), // constraint_name dbNameStr, // unique_constraint_catalog scNameStr, // unique_constraint_schema tree.NewDString(refConstraint.GetName()), // unique_constraint_name matchType, // match_option - dStringForFKAction(fk.OnUpdate), // update_rule - dStringForFKAction(fk.OnDelete), // delete_rule + dStringForFKAction(fk.OnUpdate()), // update_rule + dStringForFKAction(fk.OnDelete()), // delete_rule tbNameStr, // table_name tree.NewDString(refTable.GetName()), // referenced_table_name - ) - }) + ); err != nil { + return err + } + } + return nil }) }, } @@ -1276,26 +1270,29 @@ https://www.postgresql.org/docs/9.5/infoschema-table-constraints.html`, table catalog.TableDescriptor, tableLookup tableLookupFn, ) error { - conInfo, err := table.GetConstraintInfoWithLookup(tableLookup.getTableByID) - if err != nil { - return err - } - dbNameStr := tree.NewDString(db.GetName()) scNameStr := tree.NewDString(sc.GetName()) tbNameStr := tree.NewDString(table.GetName()) - for conName, c := range conInfo { + for _, c := range table.AllConstraints() { + kind := descpb.ConstraintTypeUnique + if c.AsCheck() != nil { + kind = descpb.ConstraintTypeCheck + } else if c.AsForeignKey() != nil { + kind = descpb.ConstraintTypeFK + } else if u := c.AsUniqueWithIndex(); u != nil && u.Primary() { + kind = descpb.ConstraintTypePK + } if err := addRow( - dbNameStr, // constraint_catalog - scNameStr, // constraint_schema - tree.NewDString(conName), // constraint_name - dbNameStr, // table_catalog - scNameStr, // table_schema - tbNameStr, // table_name - tree.NewDString(string(c.Kind)), // constraint_type - yesOrNoDatum(false), // is_deferrable - yesOrNoDatum(false), // initially_deferred + dbNameStr, // constraint_catalog + scNameStr, // constraint_schema + tree.NewDString(c.GetName()), // constraint_name + dbNameStr, // table_catalog + scNameStr, // table_schema + tbNameStr, // table_name + tree.NewDString(string(kind)), // constraint_type + yesOrNoDatum(false), // is_deferrable + yesOrNoDatum(false), // initially_deferred ); err != nil { return err } diff --git a/pkg/sql/logictest/testdata/logic_test/alter_table b/pkg/sql/logictest/testdata/logic_test/alter_table index d08565a7ba4d..8e49ad8db426 100644 --- a/pkg/sql/logictest/testdata/logic_test/alter_table +++ b/pkg/sql/logictest/testdata/logic_test/alter_table @@ -1757,23 +1757,25 @@ SELECT count(descriptor_id) # Validation for #47719 where a mutation is canceled out by a drop. statement ok -create table tColDrop (a int); +CREATE TABLE t_col_drop (a INT); + +statement error pgcode 0A000 constraint "a_auto_not_null" in the middle of being added, try again later +BEGIN; +ALTER TABLE t_col_drop ALTER COLUMN a SET NOT NULL; +ALTER TABLE t_col_drop DROP COLUMN a; statement ok -begin; -alter table tColDrop alter column a set not null; -alter table tColDrop drop column a; -commit; +ROLLBACK statement ok -drop table tColDrop; +DROP TABLE t_col_drop statement ok -begin; -create table tColDrop (a int); -alter table tColDrop alter column a set not null; -alter table tColDrop drop column a; -commit; +BEGIN; +CREATE TABLE t_col_drop (a INT); +ALTER TABLE t_col_drop ALTER COLUMN a SET NOT NULL; +ALTER TABLE t_col_drop DROP COLUMN a; +COMMIT # Validate that the schema_change_successful metric query T @@ -2325,7 +2327,7 @@ alter table const_tbl drop constraint "bob"; alter table const_tbl add constraint "steve" primary key (a, b); -- this statement statement error pq: relation \"const_tbl\" \(\d+\): unimplemented: cannot perform other schema changes in the same transaction as a primary key change.* -alter table const_tbl add constraint "bob" CHECK (a > 100); +alter table const_tbl add constraint "dave" CHECK (a > 100); statement ok rollback @@ -2333,17 +2335,10 @@ rollback statement ok alter table const_tbl add constraint "steve" CHECK (a > 100); -statement ok -BEGIN; -alter table const_tbl drop constraint "steve"; -alter table const_tbl add constraint "steve" CHECK (a > 100); -COMMIT; - statement error pq: duplicate constraint name: "steve" BEGIN; alter table const_tbl drop constraint "steve"; alter table const_tbl add constraint "steve" CHECK (a > 100); -alter table const_tbl add constraint "steve" CHECK (a > 100); COMMIT; # This is needed for following tests not to run in the aborted transaction diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index a85d48c45e78..c96dbb5d25a8 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -1312,27 +1312,27 @@ JOIN pg_catalog.pg_namespace n ON con.connamespace = n.oid WHERE n.nspname = 'public' ---- conname connamespace contype condef +t1_pkey 109 p PRIMARY_KEY_(p_ASC) +t1_a_key 109 u UNIQUE_(a_ASC) +index_key 109 u UNIQUE_(b_ASC,_c_ASC) +primary 109 p PRIMARY_KEY_(value_ASC) +primary 109 p PRIMARY_KEY_(value_ASC) +t2_pkey 109 p PRIMARY_KEY_(rowid_ASC) +fk 109 f FOREIGN_KEY_(t1_id)_REFERENCES_t1(a) +t3_pkey 109 p PRIMARY_KEY_(rowid_ASC) check_b 109 c CHECK_((b_>_11)) +check_c 109 c CHECK_((c_!=_''::STRING)) +fk 109 f FOREIGN_KEY_(a,_b)_REFERENCES_t1(b,_c) +t4_pkey 109 p PRIMARY_KEY_(rowid_ASC) +unique_a 109 u UNIQUE_WITHOUT_INDEX_(a) uwi_b_c 109 u UNIQUE_WITHOUT_INDEX_(b,_c) +uwi_b_partial 109 u UNIQUE_WITHOUT_INDEX_(b)_WHERE_(c_=_'foo'::STRING) t5_pkey 109 p PRIMARY_KEY_(rowid_ASC) fk_b_c 109 f FOREIGN_KEY_(b,_c)_REFERENCES_t4(b,_c)_MATCH_FULL_ON_UPDATE_RESTRICT -check_c 109 c CHECK_((c_!=_''::STRING)) -t6_expr_key 109 u UNIQUE_(lower(c)_ASC) -uwi_b_partial 109 u UNIQUE_WITHOUT_INDEX_(b)_WHERE_(c_=_'foo'::STRING) -t2_pkey 109 p PRIMARY_KEY_(rowid_ASC) -t3_pkey 109 p PRIMARY_KEY_(rowid_ASC) -index_key 109 u UNIQUE_(b_ASC,_c_ASC) -t1_a_key 109 u UNIQUE_(a_ASC) -unique_a 109 u UNIQUE_WITHOUT_INDEX_(a) +t5_a_fkey 109 f FOREIGN_KEY_(a)_REFERENCES_t4(a)_ON_DELETE_CASCADE t6_pkey 109 p PRIMARY_KEY_(rowid_ASC) -fk 109 f FOREIGN_KEY_(a,_b)_REFERENCES_t1(b,_c) -t1_pkey 109 p PRIMARY_KEY_(p_ASC) -t4_pkey 109 p PRIMARY_KEY_(rowid_ASC) +t6_expr_key 109 u UNIQUE_(lower(c)_ASC) mv1_pkey 109 p PRIMARY_KEY_(rowid_ASC) -t5_a_fkey 109 f FOREIGN_KEY_(a)_REFERENCES_t4(a)_ON_DELETE_CASCADE -fk 109 f FOREIGN_KEY_(t1_id)_REFERENCES_t1(a) -primary 109 p PRIMARY_KEY_(value_ASC) -primary 109 p PRIMARY_KEY_(value_ASC) query TTBBBOOO colnames,rowsort SELECT conname, contype, condeferrable, condeferred, convalidated, conrelid, contypid, conindid @@ -1341,27 +1341,27 @@ JOIN pg_catalog.pg_namespace n ON con.connamespace = n.oid WHERE n.nspname = 'public' ---- conname contype condeferrable condeferred convalidated conrelid contypid conindid +t1_pkey p false false true 110 0 3687884466 +t1_a_key u false false true 110 0 3687884465 +index_key u false false true 110 0 3687884464 +primary p false false true 111 0 2342807459 +primary p false false true 112 0 5181036 +t2_pkey p false false true 113 0 2955071325 +fk f false false true 113 0 3687884465 +t3_pkey p false false true 114 0 2695335054 check_b c false false true 114 0 0 +check_c c false false true 114 0 0 +fk f false false true 114 0 3687884464 +t4_pkey p false false true 116 0 3214807592 +unique_a u false false true 116 0 0 uwi_b_c u false false true 116 0 0 +uwi_b_partial u false false true 116 0 0 t5_pkey p false false true 117 0 1869730585 fk_b_c f false false true 117 0 0 -check_c c false false true 114 0 0 -t6_expr_key u false false true 120 0 2129466848 -uwi_b_partial u false false true 116 0 0 -t2_pkey p false false true 113 0 2955071325 -t3_pkey p false false true 114 0 2695335054 -index_key u false false true 110 0 3687884464 -t1_a_key u false false true 110 0 3687884465 -unique_a u false false true 116 0 0 +t5_a_fkey f false false true 117 0 0 t6_pkey p false false true 120 0 2129466852 -fk f false false true 114 0 3687884464 -t1_pkey p false false true 110 0 3687884466 -t4_pkey p false false true 116 0 3214807592 +t6_expr_key u false false true 120 0 2129466848 mv1_pkey p false false true 121 0 784389845 -t5_a_fkey f false false true 117 0 0 -fk f false false true 113 0 3687884465 -primary p false false true 111 0 2342807459 -primary p false false true 112 0 5181036 query T SELECT conname @@ -1378,23 +1378,23 @@ JOIN pg_catalog.pg_namespace n ON con.connamespace = n.oid WHERE n.nspname = 'public' AND contype IN ('c', 'p', 'u') ---- conname confrelid confupdtype confdeltype confmatchtype -check_b 0 NULL NULL NULL -uwi_b_c 0 NULL NULL NULL -t5_pkey 0 NULL NULL NULL -check_c 0 NULL NULL NULL -t6_expr_key 0 NULL NULL NULL -uwi_b_partial 0 NULL NULL NULL +t1_pkey 0 NULL NULL NULL +t1_a_key 0 NULL NULL NULL +index_key 0 NULL NULL NULL +primary 0 NULL NULL NULL +primary 0 NULL NULL NULL t2_pkey 0 NULL NULL NULL t3_pkey 0 NULL NULL NULL -index_key 0 NULL NULL NULL -t1_a_key 0 NULL NULL NULL +check_b 0 NULL NULL NULL +check_c 0 NULL NULL NULL +t4_pkey 0 NULL NULL NULL unique_a 0 NULL NULL NULL +uwi_b_c 0 NULL NULL NULL +uwi_b_partial 0 NULL NULL NULL +t5_pkey 0 NULL NULL NULL t6_pkey 0 NULL NULL NULL -t1_pkey 0 NULL NULL NULL -t4_pkey 0 NULL NULL NULL +t6_expr_key 0 NULL NULL NULL mv1_pkey 0 NULL NULL NULL -primary 0 NULL NULL NULL -primary 0 NULL NULL NULL query TOTTT colnames,rowsort SELECT conname, confrelid, confupdtype, confdeltype, confmatchtype @@ -1416,27 +1416,27 @@ JOIN pg_catalog.pg_namespace n ON con.connamespace = n.oid WHERE n.nspname = 'public' ---- conname conislocal coninhcount connoinherit conkey +t1_pkey true 0 true {1} +t1_a_key true 0 true {2} +index_key true 0 true {3,4} +primary true 0 true {1} +primary true 0 true {1} +t2_pkey true 0 true {2} +fk true 0 true {1} +t3_pkey true 0 true {4} check_b true 0 true {2} +check_c true 0 true {3} +fk true 0 true {1,2} +t4_pkey true 0 true {4} +unique_a true 0 true NULL uwi_b_c true 0 true NULL +uwi_b_partial true 0 true NULL t5_pkey true 0 true {4} fk_b_c true 0 true {2,3} -check_c true 0 true {3} -t6_expr_key true 0 true {7} -uwi_b_partial true 0 true NULL -t2_pkey true 0 true {2} -t3_pkey true 0 true {4} -index_key true 0 true {3,4} -t1_a_key true 0 true {2} -unique_a true 0 true NULL +t5_a_fkey true 0 true {1} t6_pkey true 0 true {6} -fk true 0 true {1,2} -t1_pkey true 0 true {1} -t4_pkey true 0 true {4} +t6_expr_key true 0 true {7} mv1_pkey true 0 true {2} -t5_a_fkey true 0 true {1} -fk true 0 true {1} -primary true 0 true {1} -primary true 0 true {1} query TTTTTTTTT colnames SELECT conname, confkey, conpfeqop, conppeqop, conffeqop, conexclop, conbin, consrc, pg_get_constraintdef(con.oid) as condef diff --git a/pkg/sql/logictest/testdata/logic_test/schema_change_in_txn b/pkg/sql/logictest/testdata/logic_test/schema_change_in_txn index ed5b0f9d6d43..d54eb916d9e9 100644 --- a/pkg/sql/logictest/testdata/logic_test/schema_change_in_txn +++ b/pkg/sql/logictest/testdata/logic_test/schema_change_in_txn @@ -1036,7 +1036,7 @@ ALTER TABLE check_table ADD g INT statement ok ALTER TABLE check_table ADD CONSTRAINT g_0 CHECK (g > 0) -statement error unimplemented: constraint "g_0" in the middle of being added, try again later +statement error pgcode 0A000 constraint "g_0" in the middle of being added, try again later ALTER TABLE check_table DROP COLUMN g statement ok diff --git a/pkg/sql/opt_catalog.go b/pkg/sql/opt_catalog.go index a03cb86b9d59..da9ea635cbda 100644 --- a/pkg/sql/opt_catalog.go +++ b/pkg/sql/opt_catalog.go @@ -854,17 +854,16 @@ func newOptTable( // Add unique without index constraints. Constraints for implicitly // partitioned unique indexes will be added below. - ot.uniqueConstraints = make([]optUniqueConstraint, 0, len(ot.desc.GetUniqueWithoutIndexConstraints())) - for i := range ot.desc.GetUniqueWithoutIndexConstraints() { - u := &ot.desc.GetUniqueWithoutIndexConstraints()[i] - ot.uniqueConstraints = append(ot.uniqueConstraints, optUniqueConstraint{ - name: u.Name, + ot.uniqueConstraints = make([]optUniqueConstraint, len(ot.desc.EnforcedUniqueConstraintsWithoutIndex())) + for i, u := range ot.desc.EnforcedUniqueConstraintsWithoutIndex() { + ot.uniqueConstraints[i] = optUniqueConstraint{ + name: u.GetName(), table: ot.ID(), - columns: u.ColumnIDs, - predicate: u.Predicate, + columns: u.CollectKeyColumnIDs().Ordered(), + predicate: u.GetPredicate(), withoutIndex: true, - validity: u.Validity, - }) + validity: u.GetConstraintValidity(), + } } // Build the indexes. @@ -957,34 +956,32 @@ func newOptTable( } } - _ = ot.desc.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { + for _, fk := range ot.desc.OutboundForeignKeys() { ot.outboundFKs = append(ot.outboundFKs, optForeignKeyConstraint{ - name: fk.Name, + name: fk.GetName(), originTable: ot.ID(), - originColumns: fk.OriginColumnIDs, - referencedTable: cat.StableID(fk.ReferencedTableID), - referencedColumns: fk.ReferencedColumnIDs, - validity: fk.Validity, - match: fk.Match, - deleteAction: fk.OnDelete, - updateAction: fk.OnUpdate, + originColumns: fk.ForeignKeyDesc().OriginColumnIDs, + referencedTable: cat.StableID(fk.GetReferencedTableID()), + referencedColumns: fk.ForeignKeyDesc().ReferencedColumnIDs, + validity: fk.GetConstraintValidity(), + match: fk.Match(), + deleteAction: fk.OnDelete(), + updateAction: fk.OnUpdate(), }) - return nil - }) - _ = ot.desc.ForeachInboundFK(func(fk *descpb.ForeignKeyConstraint) error { + } + for _, fk := range ot.desc.InboundForeignKeys() { ot.inboundFKs = append(ot.inboundFKs, optForeignKeyConstraint{ - name: fk.Name, - originTable: cat.StableID(fk.OriginTableID), - originColumns: fk.OriginColumnIDs, + name: fk.GetName(), + originTable: cat.StableID(fk.GetOriginTableID()), + originColumns: fk.ForeignKeyDesc().OriginColumnIDs, referencedTable: ot.ID(), - referencedColumns: fk.ReferencedColumnIDs, - validity: fk.Validity, - match: fk.Match, - deleteAction: fk.OnDelete, - updateAction: fk.OnUpdate, + referencedColumns: fk.ForeignKeyDesc().ReferencedColumnIDs, + validity: fk.GetConstraintValidity(), + match: fk.Match(), + deleteAction: fk.OnDelete(), + updateAction: fk.OnUpdate(), }) - return nil - }) + } ot.primaryFamily.init(ot, &desc.GetFamilies()[0]) ot.families = make([]optFamily, len(desc.GetFamilies())-1) @@ -1018,12 +1015,12 @@ func newOptTable( } } // Move all existing and synthesized checks into the opt table. - activeChecks := desc.ActiveChecks() + activeChecks := desc.EnforcedCheckConstraints() ot.checkConstraints = make([]cat.CheckConstraint, 0, len(activeChecks)+len(synthesizedChecks)) for i := range activeChecks { ot.checkConstraints = append(ot.checkConstraints, cat.CheckConstraint{ - Constraint: activeChecks[i].Expr, - Validated: activeChecks[i].Validity == descpb.ConstraintValidity_Validated, + Constraint: activeChecks[i].GetExpr(), + Validated: activeChecks[i].GetConstraintValidity() == descpb.ConstraintValidity_Validated, }) } ot.checkConstraints = append(ot.checkConstraints, synthesizedChecks...) @@ -2214,15 +2211,15 @@ func (ot *optVirtualTable) Statistic(i int) cat.TableStatistic { // CheckCount is part of the cat.Table interface. func (ot *optVirtualTable) CheckCount() int { - return len(ot.desc.ActiveChecks()) + return len(ot.desc.EnforcedCheckConstraints()) } // Check is part of the cat.Table interface. func (ot *optVirtualTable) Check(i int) cat.CheckConstraint { - check := ot.desc.ActiveChecks()[i] + check := ot.desc.EnforcedCheckConstraints()[i] return cat.CheckConstraint{ - Constraint: check.Expr, - Validated: check.Validity == descpb.ConstraintValidity_Validated, + Constraint: check.GetExpr(), + Validated: check.GetConstraintValidity() == descpb.ConstraintValidity_Validated, } } diff --git a/pkg/sql/pg_catalog.go b/pkg/sql/pg_catalog.go index 97ef63286827..ac54708ccd63 100644 --- a/pkg/sql/pg_catalog.go +++ b/pkg/sql/pg_catalog.go @@ -700,8 +700,8 @@ https://www.postgresql.org/docs/9.5/catalog-pg-class.html`, relPersistence, // relpersistence tree.MakeDBool(tree.DBool(table.IsTemporary())), // relistemp relKind, // relkind - tree.NewDInt(tree.DInt(len(table.AccessibleColumns()))), // relnatts - tree.NewDInt(tree.DInt(len(table.GetChecks()))), // relchecks + tree.NewDInt(tree.DInt(len(table.AccessibleColumns()))), // relnatts + tree.NewDInt(tree.DInt(len(table.EnforcedCheckConstraints()))), // relchecks tree.DBoolFalse, // relhasoids tree.MakeDBool(tree.DBool(table.IsPhysicalTable())), // relhaspkey tree.DBoolFalse, // relhasrules @@ -870,13 +870,9 @@ func populateTableConstraints( tableLookup simpleSchemaResolver, addRow func(...tree.Datum) error, ) error { - conInfo, err := table.GetConstraintInfoWithLookup(tableLookup.getTableByID) - if err != nil { - return err - } namespaceOid := schemaOid(sc.GetID()) tblOid := tableOid(table.GetID()) - for conName, con := range conInfo { + for _, c := range table.AllConstraints() { conoid := tree.DNull contype := tree.DNull conindid := oidZero @@ -892,159 +888,141 @@ func populateTableConstraints( // Determine constraint kind-specific fields. var err error - switch con.Kind { - case descpb.ConstraintTypePK: - if !p.SessionData().ShowPrimaryKeyConstraintOnNotVisibleColumns { - colHiddenMap := make(map[descpb.ColumnID]bool, len(table.AllColumns())) - for i := range table.AllColumns() { - col := table.AllColumns()[i] - colHiddenMap[col.GetID()] = col.IsHidden() - } - allHidden := true - for _, colIdx := range con.Index.KeyColumnIDs { - if !colHiddenMap[colIdx] { - allHidden = false - break + if uwi := c.AsUniqueWithIndex(); uwi != nil { + conindid = h.IndexOid(table.GetID(), uwi.GetID()) + var err error + if conkey, err = colIDArrayToDatum(uwi.IndexDesc().KeyColumnIDs); err != nil { + return err + } + if uwi.Primary() { + if !p.SessionData().ShowPrimaryKeyConstraintOnNotVisibleColumns { + allHidden := true + for _, col := range table.IndexKeyColumns(uwi) { + if !col.IsHidden() { + allHidden = false + break + } + } + if allHidden { + continue } } - if allHidden { - continue + conoid = h.PrimaryKeyConstraintOid(db.GetID(), sc.GetID(), table.GetID(), uwi) + contype = conTypePKey + condef = tree.NewDString(tabledesc.PrimaryKeyString(table)) + } else { + f := tree.NewFmtCtx(tree.FmtSimple) + conoid = h.UniqueConstraintOid(db.GetID(), sc.GetID(), table.GetID(), uwi) + contype = conTypeUnique + f.WriteString("UNIQUE (") + if err := catformat.FormatIndexElements( + ctx, table, uwi.IndexDesc(), f, p.SemaCtx(), p.SessionData(), + ); err != nil { + return err } + f.WriteByte(')') + if uwi.IsPartial() { + pred, err := schemaexpr.FormatExprForDisplay(ctx, table, uwi.GetPredicate(), p.SemaCtx(), p.SessionData(), tree.FmtPGCatalog) + if err != nil { + return err + } + f.WriteString(fmt.Sprintf(" WHERE (%s)", pred)) + } + condef = tree.NewDString(f.CloseAndGetString()) } - conoid = h.PrimaryKeyConstraintOid(db.GetID(), sc.GetID(), table.GetID(), con.Index) - contype = conTypePKey - conindid = h.IndexOid(table.GetID(), con.Index.ID) - - var err error - if conkey, err = colIDArrayToDatum(con.Index.KeyColumnIDs); err != nil { - return err - } - condef = tree.NewDString(tabledesc.PrimaryKeyString(table)) - - case descpb.ConstraintTypeFK: - conoid = h.ForeignKeyConstraintOid(db.GetID(), sc.GetID(), table.GetID(), con.FK) + } else if fk := c.AsForeignKey(); fk != nil { + conoid = h.ForeignKeyConstraintOid(db.GetID(), sc.GetID(), table.GetID(), fk) contype = conTypeFK // Foreign keys don't have a single linked index. Pick the first one // that matches on the referenced table. - referencedTable, err := tableLookup.getTableByID(con.FK.ReferencedTableID) + referencedTable, err := tableLookup.getTableByID(fk.GetReferencedTableID()) if err != nil { return err } - if refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint( - referencedTable, con.FK.ReferencedColumnIDs, - ); err != nil { + if refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint(referencedTable, fk); err != nil { // We couldn't find a unique constraint that matched. This shouldn't // happen. log.Warningf(ctx, "broken fk reference: %v", err) - } else if idx, ok := refConstraint.(*descpb.IndexDescriptor); ok { - conindid = h.IndexOid(con.ReferencedTable.ID, idx.ID) + } else if idx := refConstraint.AsUniqueWithIndex(); idx != nil { + conindid = h.IndexOid(referencedTable.GetID(), idx.GetID()) } - confrelid = tableOid(con.ReferencedTable.ID) - if r, ok := fkActionMap[con.FK.OnUpdate]; ok { + confrelid = tableOid(referencedTable.GetID()) + if r, ok := fkActionMap[fk.OnUpdate()]; ok { confupdtype = r } - if r, ok := fkActionMap[con.FK.OnDelete]; ok { + if r, ok := fkActionMap[fk.OnDelete()]; ok { confdeltype = r } - if r, ok := fkMatchMap[con.FK.Match]; ok { + if r, ok := fkMatchMap[fk.Match()]; ok { confmatchtype = r } - if conkey, err = colIDArrayToDatum(con.FK.OriginColumnIDs); err != nil { + if conkey, err = colIDArrayToDatum(fk.ForeignKeyDesc().OriginColumnIDs); err != nil { return err } - if confkey, err = colIDArrayToDatum(con.FK.ReferencedColumnIDs); err != nil { + if confkey, err = colIDArrayToDatum(fk.ForeignKeyDesc().ReferencedColumnIDs); err != nil { return err } var buf bytes.Buffer if err := showForeignKeyConstraint( &buf, db.GetName(), - table, con.FK, + table, fk.ForeignKeyDesc(), tableLookup, p.extendedEvalCtx.SessionData().SearchPath, ); err != nil { return err } condef = tree.NewDString(buf.String()) - - case descpb.ConstraintTypeUnique: + } else if uwoi := c.AsUniqueWithoutIndex(); uwoi != nil { contype = conTypeUnique f := tree.NewFmtCtx(tree.FmtSimple) - if con.Index != nil { - conoid = h.UniqueConstraintOid(db.GetID(), sc.GetID(), table.GetID(), con.Index.ID) - conindid = h.IndexOid(table.GetID(), con.Index.ID) - var err error - if conkey, err = colIDArrayToDatum(con.Index.KeyColumnIDs); err != nil { - return err - } - f.WriteString("UNIQUE (") - if err := catformat.FormatIndexElements( - ctx, table, con.Index, f, p.SemaCtx(), p.SessionData(), - ); err != nil { - return err - } - f.WriteByte(')') - if con.Index.IsPartial() { - pred, err := schemaexpr.FormatExprForDisplay(ctx, table, con.Index.Predicate, p.SemaCtx(), p.SessionData(), tree.FmtPGCatalog) - if err != nil { - return err - } - f.WriteString(fmt.Sprintf(" WHERE (%s)", pred)) - } - } else if con.UniqueWithoutIndexConstraint != nil { - conoid = h.UniqueWithoutIndexConstraintOid( - db.GetID(), sc.GetID(), table.GetID(), con.UniqueWithoutIndexConstraint, - ) - f.WriteString("UNIQUE WITHOUT INDEX (") - colNames, err := table.NamesForColumnIDs(con.UniqueWithoutIndexConstraint.ColumnIDs) + conoid = h.UniqueWithoutIndexConstraintOid( + db.GetID(), sc.GetID(), table.GetID(), uwoi, + ) + f.WriteString("UNIQUE WITHOUT INDEX (") + colNames, err := table.NamesForColumnIDs(uwoi.UniqueWithoutIndexDesc().ColumnIDs) + if err != nil { + return err + } + f.WriteString(strings.Join(colNames, ", ")) + f.WriteByte(')') + if !uwoi.IsConstraintValidated() { + f.WriteString(" NOT VALID") + } + if uwoi.GetPredicate() != "" { + pred, err := schemaexpr.FormatExprForDisplay(ctx, table, uwoi.GetPredicate(), p.SemaCtx(), p.SessionData(), tree.FmtPGCatalog) if err != nil { return err } - f.WriteString(strings.Join(colNames, ", ")) - f.WriteByte(')') - if con.UniqueWithoutIndexConstraint.Validity != descpb.ConstraintValidity_Validated { - f.WriteString(" NOT VALID") - } - if con.UniqueWithoutIndexConstraint.Predicate != "" { - pred, err := schemaexpr.FormatExprForDisplay(ctx, table, con.UniqueWithoutIndexConstraint.Predicate, p.SemaCtx(), p.SessionData(), tree.FmtPGCatalog) - if err != nil { - return err - } - f.WriteString(fmt.Sprintf(" WHERE (%s)", pred)) - } - } else { - return errors.AssertionFailedf( - "Index or UniqueWithoutIndexConstraint must be non-nil for a unique constraint", - ) + f.WriteString(fmt.Sprintf(" WHERE (%s)", pred)) } condef = tree.NewDString(f.CloseAndGetString()) - - case descpb.ConstraintTypeCheck: - conoid = h.CheckConstraintOid(db.GetID(), sc.GetID(), table.GetID(), con.CheckConstraint) + } else if ck := c.AsCheck(); ck != nil { + conoid = h.CheckConstraintOid(db.GetID(), sc.GetID(), table.GetID(), ck) contype = conTypeCheck - if conkey, err = colIDArrayToDatum(con.CheckConstraint.ColumnIDs); err != nil { + if conkey, err = colIDArrayToDatum(ck.CheckDesc().ColumnIDs); err != nil { return err } - displayExpr, err := schemaexpr.FormatExprForDisplay(ctx, table, con.Details, &p.semaCtx, p.SessionData(), tree.FmtPGCatalog) + displayExpr, err := schemaexpr.FormatExprForDisplay(ctx, table, ck.GetExpr(), &p.semaCtx, p.SessionData(), tree.FmtPGCatalog) if err != nil { return err } consrc = tree.NewDString(fmt.Sprintf("(%s)", displayExpr)) conbin = consrc validity := "" - if con.CheckConstraint.Validity != descpb.ConstraintValidity_Validated { + if !ck.IsConstraintValidated() { validity = " NOT VALID" } condef = tree.NewDString(fmt.Sprintf("CHECK ((%s))%s", displayExpr, validity)) } if err := addRow( - conoid, // oid - dNameOrNull(conName), // conname - namespaceOid, // connamespace - contype, // contype - tree.DBoolFalse, // condeferrable - tree.DBoolFalse, // condeferred - tree.MakeDBool(tree.DBool(!con.Unvalidated)), // convalidated + conoid, // oid + dNameOrNull(c.GetName()), // conname + namespaceOid, // connamespace + contype, // contype + tree.DBoolFalse, // condeferrable + tree.DBoolFalse, // condeferred + tree.MakeDBool(tree.DBool(!c.IsConstraintUnvalidated())), // convalidated tblOid, // conrelid oidZero, // contypid conindid, // conindid @@ -1518,32 +1496,22 @@ https://www.postgresql.org/docs/9.5/catalog-pg-depend.html`, } } - conInfo, err := table.GetConstraintInfoWithLookup(tableLookup.getTableByID) - if err != nil { - return err - } - for _, con := range conInfo { - if con.Kind != descpb.ConstraintTypeFK { - continue - } - + for _, fk := range table.OutboundForeignKeys() { // Foreign keys don't have a single linked index. Pick the first one // that matches on the referenced table. - referencedTable, err := tableLookup.getTableByID(con.FK.ReferencedTableID) + referencedTable, err := tableLookup.getTableByID(fk.GetReferencedTableID()) if err != nil { return err } refObjID := oidZero - if refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint( - referencedTable, con.FK.ReferencedColumnIDs, - ); err != nil { + if refConstraint, err := tabledesc.FindFKReferencedUniqueConstraint(referencedTable, fk); err != nil { // We couldn't find a unique constraint that matched. This shouldn't // happen. log.Warningf(ctx, "broken fk reference: %v", err) - } else if idx, ok := refConstraint.(*descpb.IndexDescriptor); ok { - refObjID = h.IndexOid(con.ReferencedTable.ID, idx.ID) + } else if idx := refConstraint.AsUniqueWithIndex(); idx != nil { + refObjID = h.IndexOid(referencedTable.GetID(), idx.GetID()) } - constraintOid := h.ForeignKeyConstraintOid(db.GetID(), sc.GetID(), table.GetID(), con.FK) + constraintOid := h.ForeignKeyConstraintOid(db.GetID(), sc.GetID(), table.GetID(), fk) if err := addRow( pgConstraintTableOid, // classid @@ -1639,18 +1607,11 @@ https://www.postgresql.org/docs/9.5/catalog-pg-description.html`, if err != nil { return err } - constraints, err := tableDesc.GetConstraintInfo() + c, err := tableDesc.FindConstraintWithID(descpb.ConstraintID(tree.MustBeDInt(objSubID))) if err != nil { return err } - var constraint descpb.ConstraintDetail - for _, constraintToCheck := range constraints { - if constraintToCheck.ConstraintID == descpb.ConstraintID(tree.MustBeDInt(objSubID)) { - constraint = constraintToCheck - break - } - } - objID = getOIDFromConstraint(constraint, dbContext.GetID(), schema.GetID(), tableDesc) + objID = getOIDFromConstraint(c, dbContext.GetID(), schema.GetID(), tableDesc) objSubID = tree.DZero classOid = tree.NewDOid(catconstants.PgCatalogConstraintTableID) case keys.IndexCommentType: @@ -1673,48 +1634,46 @@ https://www.postgresql.org/docs/9.5/catalog-pg-description.html`, } func getOIDFromConstraint( - constraint descpb.ConstraintDetail, - dbID descpb.ID, - scID descpb.ID, - tableDesc catalog.TableDescriptor, + constraint catalog.Constraint, dbID descpb.ID, scID descpb.ID, tableDesc catalog.TableDescriptor, ) *tree.DOid { hasher := makeOidHasher() tableID := tableDesc.GetID() var oid *tree.DOid - if constraint.CheckConstraint != nil { + if ck := constraint.AsCheck(); ck != nil { oid = hasher.CheckConstraintOid( dbID, scID, tableID, - constraint.CheckConstraint) - } else if constraint.FK != nil { + ck, + ) + } else if fk := constraint.AsForeignKey(); fk != nil { oid = hasher.ForeignKeyConstraintOid( dbID, scID, tableID, - constraint.FK, + fk, ) - } else if constraint.UniqueWithoutIndexConstraint != nil { + } else if uc := constraint.AsUniqueWithoutIndex(); uc != nil { oid = hasher.UniqueWithoutIndexConstraintOid( dbID, scID, tableID, - constraint.UniqueWithoutIndexConstraint, + uc, ) - } else if constraint.Index != nil { - if constraint.Index.ID == tableDesc.GetPrimaryIndexID() { + } else if ic := constraint.AsUniqueWithIndex(); ic != nil { + if ic.GetID() == tableDesc.GetPrimaryIndexID() { oid = hasher.PrimaryKeyConstraintOid( dbID, scID, tableID, - constraint.Index, + ic, ) } else { oid = hasher.UniqueConstraintOid( dbID, scID, tableID, - constraint.Index.ID, + ic, ) } } @@ -4495,19 +4454,19 @@ func (h oidHasher) writeIndex(indexID descpb.IndexID) { h.writeUInt32(uint32(indexID)) } -func (h oidHasher) writeUniqueConstraint(uc *descpb.UniqueWithoutIndexConstraint) { - h.writeUInt32(uint32(uc.TableID)) - h.writeStr(uc.Name) +func (h oidHasher) writeUniqueConstraint(uc catalog.UniqueWithoutIndexConstraint) { + h.writeUInt32(uint32(uc.ParentTableID())) + h.writeStr(uc.GetName()) } -func (h oidHasher) writeCheckConstraint(check *descpb.TableDescriptor_CheckConstraint) { - h.writeStr(check.Name) - h.writeStr(check.Expr) +func (h oidHasher) writeCheckConstraint(check catalog.CheckConstraint) { + h.writeStr(check.GetName()) + h.writeStr(check.GetExpr()) } -func (h oidHasher) writeForeignKeyConstraint(fk *descpb.ForeignKeyConstraint) { - h.writeUInt32(uint32(fk.ReferencedTableID)) - h.writeStr(fk.Name) +func (h oidHasher) writeForeignKeyConstraint(fk catalog.ForeignKeyConstraint) { + h.writeUInt32(uint32(fk.GetReferencedTableID())) + h.writeStr(fk.GetName()) } func (h oidHasher) IndexOid(tableID descpb.ID, indexID descpb.IndexID) *tree.DOid { @@ -4525,7 +4484,7 @@ func (h oidHasher) ColumnOid(tableID descpb.ID, columnID descpb.ColumnID) *tree. } func (h oidHasher) CheckConstraintOid( - dbID descpb.ID, scID descpb.ID, tableID descpb.ID, check *descpb.TableDescriptor_CheckConstraint, + dbID descpb.ID, scID descpb.ID, tableID descpb.ID, check catalog.CheckConstraint, ) *tree.DOid { h.writeTypeTag(checkConstraintTypeTag) h.writeDB(dbID) @@ -4536,18 +4495,18 @@ func (h oidHasher) CheckConstraintOid( } func (h oidHasher) PrimaryKeyConstraintOid( - dbID descpb.ID, scID descpb.ID, tableID descpb.ID, pkey *descpb.IndexDescriptor, + dbID descpb.ID, scID descpb.ID, tableID descpb.ID, pkey catalog.UniqueWithIndexConstraint, ) *tree.DOid { h.writeTypeTag(pKeyConstraintTypeTag) h.writeDB(dbID) h.writeSchema(scID) h.writeTable(tableID) - h.writeIndex(pkey.ID) + h.writeIndex(pkey.GetID()) return h.getOid() } func (h oidHasher) ForeignKeyConstraintOid( - dbID descpb.ID, scID descpb.ID, tableID descpb.ID, fk *descpb.ForeignKeyConstraint, + dbID descpb.ID, scID descpb.ID, tableID descpb.ID, fk catalog.ForeignKeyConstraint, ) *tree.DOid { h.writeTypeTag(fkConstraintTypeTag) h.writeDB(dbID) @@ -4558,7 +4517,7 @@ func (h oidHasher) ForeignKeyConstraintOid( } func (h oidHasher) UniqueWithoutIndexConstraintOid( - dbID descpb.ID, scID descpb.ID, tableID descpb.ID, uc *descpb.UniqueWithoutIndexConstraint, + dbID descpb.ID, scID descpb.ID, tableID descpb.ID, uc catalog.UniqueWithoutIndexConstraint, ) *tree.DOid { h.writeTypeTag(uniqueConstraintTypeTag) h.writeDB(dbID) @@ -4569,13 +4528,13 @@ func (h oidHasher) UniqueWithoutIndexConstraintOid( } func (h oidHasher) UniqueConstraintOid( - dbID descpb.ID, scID descpb.ID, tableID descpb.ID, indexID descpb.IndexID, + dbID descpb.ID, scID descpb.ID, tableID descpb.ID, uwi catalog.UniqueWithIndexConstraint, ) *tree.DOid { h.writeTypeTag(uniqueConstraintTypeTag) h.writeDB(dbID) h.writeSchema(scID) h.writeTable(tableID) - h.writeIndex(indexID) + h.writeIndex(uwi.GetID()) return h.getOid() } diff --git a/pkg/sql/pgwire/testdata/pgtest/notice b/pkg/sql/pgwire/testdata/pgtest/notice index 1193cf6e078e..1b70b3feafb8 100644 --- a/pkg/sql/pgwire/testdata/pgtest/notice +++ b/pkg/sql/pgwire/testdata/pgtest/notice @@ -55,7 +55,7 @@ Query {"String": "DROP INDEX t_x_idx"} until crdb_only CommandComplete ---- -{"Severity":"NOTICE","SeverityUnlocalized":"NOTICE","Code":"00000","Message":"the data for dropped indexes is reclaimed asynchronously","Detail":"","Hint":"The reclamation delay can be customized in the zone configuration for the table.","Position":0,"InternalPosition":0,"InternalQuery":"","Where":"","SchemaName":"","TableName":"","ColumnName":"","DataTypeName":"","ConstraintName":"","File":"drop_index.go","Line":547,"Routine":"dropIndexByName","UnknownFields":null} +{"Severity":"NOTICE","SeverityUnlocalized":"NOTICE","Code":"00000","Message":"the data for dropped indexes is reclaimed asynchronously","Detail":"","Hint":"The reclamation delay can be customized in the zone configuration for the table.","Position":0,"InternalPosition":0,"InternalQuery":"","Where":"","SchemaName":"","TableName":"","ColumnName":"","DataTypeName":"","ConstraintName":"","File":"drop_index.go","Line":530,"Routine":"dropIndexByName","UnknownFields":null} {"Type":"CommandComplete","CommandTag":"DROP INDEX"} until noncrdb_only diff --git a/pkg/sql/rename_table.go b/pkg/sql/rename_table.go index f18b2e1edcf4..c286eaa6a94e 100644 --- a/pkg/sql/rename_table.go +++ b/pkg/sql/rename_table.go @@ -311,13 +311,13 @@ func (n *renameTableNode) checkForCrossDbReferences( // Checks inbound / outbound foreign key references for cross DB references. // The refTableID flag determines if the reference or origin field are checked. - checkFkForCrossDbDep := func(fk *descpb.ForeignKeyConstraint, refTableID bool) error { + checkFkForCrossDbDep := func(fk catalog.ForeignKeyConstraint, refTableID bool) error { if allowCrossDatabaseFKs.Get(&p.execCfg.Settings.SV) { return nil } - tableID := fk.ReferencedTableID + tableID := fk.GetReferencedTableID() if !refTableID { - tableID = fk.OriginTableID + tableID = fk.GetOriginTableID() } referencedTable, err := p.Descriptors().GetImmutableTableByID(ctx, p.txn, tableID, @@ -339,7 +339,7 @@ func (n *renameTableNode) checkForCrossDbReferences( pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, "a foreign key constraint %q will exist between databases after rename "+ "(see the '%s' cluster setting)", - fk.Name, + fk.GetName(), allowCrossDatabaseFKsSetting), crossDBReferenceDeprecationHint(), ) @@ -477,18 +477,15 @@ func (n *renameTableNode) checkForCrossDbReferences( // For tables check if any outbound or inbound foreign key references would // be impacted. if tableDesc.IsTable() { - err := tableDesc.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { - return checkFkForCrossDbDep(fk, true) - }) - if err != nil { - return err + for _, fk := range tableDesc.OutboundForeignKeys() { + if err := checkFkForCrossDbDep(fk, false); err != nil { + return err + } } - - err = tableDesc.ForeachInboundFK(func(fk *descpb.ForeignKeyConstraint) error { - return checkFkForCrossDbDep(fk, false) - }) - if err != nil { - return err + for _, fk := range tableDesc.InboundForeignKeys() { + if err := checkFkForCrossDbDep(fk, false); err != nil { + return err + } } // If cross database sequence owners are not allowed, then // check if any column owns a sequence. diff --git a/pkg/sql/schema_changer.go b/pkg/sql/schema_changer.go index c8bcf6aedb99..6a836941c5e3 100644 --- a/pkg/sql/schema_changer.go +++ b/pkg/sql/schema_changer.go @@ -1428,19 +1428,18 @@ func (sc *SchemaChanger) done(ctx context.Context) error { } } } - if constraint := m.AsConstraint(); constraint != nil && constraint.Adding() { - if fk := constraint.AsForeignKey(); fk != nil && fk.Validity == descpb.ConstraintValidity_Unvalidated { - // Add backreference on the referenced table (which could be the same table) - backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.ReferencedTableID, txn) - if err != nil { + if fk := m.AsForeignKey(); fk != nil && fk.Adding() && + fk.GetConstraintValidity() == descpb.ConstraintValidity_Unvalidated { + // Add backreference on the referenced table (which could be the same table) + backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.GetReferencedTableID(), txn) + if err != nil { + return err + } + backrefTable.InboundFKs = append(backrefTable.InboundFKs, *fk.ForeignKeyDesc()) + if backrefTable != scTable { + if err := descsCol.WriteDescToBatch(ctx, kvTrace, backrefTable, b); err != nil { return err } - backrefTable.InboundFKs = append(backrefTable.InboundFKs, *fk) - if backrefTable != scTable { - if err := descsCol.WriteDescToBatch(ctx, kvTrace, backrefTable, b); err != nil { - return err - } - } } } @@ -2002,14 +2001,14 @@ func (sc *SchemaChanger) maybeReverseMutations(ctx context.Context, causingError // If the mutation is for validating a constraint that is being added, // drop the constraint because validation has failed. - if constraint := m.AsConstraint(); constraint != nil && constraint.Adding() { + if constraint := m.AsConstraintWithoutIndex(); constraint != nil && constraint.Adding() { log.Warningf(ctx, "dropping constraint %s", constraint) if err := sc.maybeDropValidatingConstraint(ctx, scTable, constraint); err != nil { return err } // Get the foreign key backreferences to remove. if fk := constraint.AsForeignKey(); fk != nil { - backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.ReferencedTableID, txn) + backrefTable, err := descsCol.GetMutableTableVersionByID(ctx, fk.GetReferencedTableID(), txn) if err != nil { return err } @@ -3222,11 +3221,9 @@ func isCurrentMutationDiscarded( colToCheck := make([]descpb.ColumnID, 0, 1) // Both NOT NULL related updates and check constraint updates // involving this column will get canceled out by a drop column. - if constraint := currentMutation.AsConstraint(); constraint != nil { - if colID := constraint.NotNullColumnID(); colID != 0 { - colToCheck = append(colToCheck, colID) - } else if ck := constraint.AsCheck(); ck != nil { - colToCheck = ck.ColumnIDs + if ck := currentMutation.AsCheck(); ck != nil { + for i, n := 0, ck.NumReferencedColumns(); i < n; i++ { + colToCheck = append(colToCheck, ck.GetReferencedColumnID(i)) } } diff --git a/pkg/sql/schema_changer_test.go b/pkg/sql/schema_changer_test.go index 02aecc693cd7..2d1c869a53cd 100644 --- a/pkg/sql/schema_changer_test.go +++ b/pkg/sql/schema_changer_test.go @@ -1276,8 +1276,8 @@ CREATE TABLE t.test ( // Read table descriptor. tableDesc := desctestutils.TestingGetPublicTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") - if len(tableDesc.GetChecks()) != 3 { - t.Fatalf("Expected 3 checks but got %d ", len(tableDesc.GetChecks())) + if len(tableDesc.EnforcedCheckConstraints()) != 3 { + t.Fatalf("Expected 3 checks but got %d ", len(tableDesc.EnforcedCheckConstraints())) } if _, err := sqlDB.Exec("ALTER TABLE t.test DROP v"); err != nil { @@ -1287,16 +1287,16 @@ CREATE TABLE t.test ( // Re-read table descriptor. tableDesc = desctestutils.TestingGetPublicTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") // Only check_ab should remain - if len(tableDesc.GetChecks()) != 1 { + if len(tableDesc.EnforcedCheckConstraints()) != 1 { checkExprs := make([]string, 0) - for i := range tableDesc.GetChecks() { - checkExprs = append(checkExprs, tableDesc.GetChecks()[i].Expr) + for i := range tableDesc.EnforcedCheckConstraints() { + checkExprs = append(checkExprs, tableDesc.EnforcedCheckConstraints()[i].GetExpr()) } - t.Fatalf("Expected 1 check but got %d with CHECK expr %s ", len(tableDesc.GetChecks()), strings.Join(checkExprs, ", ")) + t.Fatalf("Expected 1 check but got %d with CHECK expr %s ", len(tableDesc.EnforcedCheckConstraints()), strings.Join(checkExprs, ", ")) } - if tableDesc.GetChecks()[0].Name != "check_ab" { - t.Fatalf("Only check_ab should remain, got: %s ", tableDesc.GetChecks()[0].Name) + if tableDesc.EnforcedCheckConstraints()[0].GetName() != "check_ab" { + t.Fatalf("Only check_ab should remain, got: %s ", tableDesc.EnforcedCheckConstraints()[0].GetName()) } // Test that a constraint being added prevents the column from being dropped. @@ -1780,7 +1780,7 @@ CREATE TABLE t.test (k INT PRIMARY KEY, v INT); // Check that constraints are cleaned up on the latest version of the // descriptor. tableDesc = desctestutils.TestingGetPublicTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") - if checks := tableDesc.AllActiveAndInactiveChecks(); len(checks) > 0 { + if checks := tableDesc.CheckConstraints(); len(checks) > 0 { t.Fatalf("found checks %+v", checks) } } @@ -4973,7 +4973,7 @@ func TestCancelSchemaChange(t *testing.T) { // Check that constraints are cleaned up. tableDesc = desctestutils.TestingGetPublicTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") - if checks := tableDesc.AllActiveAndInactiveChecks(); len(checks) != 1 { + if checks := tableDesc.CheckConstraints(); len(checks) != 1 { t.Fatalf("expected 1 check, found %+v", checks) } } diff --git a/pkg/sql/schemachanger/scdecomp/decomp.go b/pkg/sql/schemachanger/scdecomp/decomp.go index 920384264637..41abfd52f9e5 100644 --- a/pkg/sql/schemachanger/scdecomp/decomp.go +++ b/pkg/sql/schemachanger/scdecomp/decomp.go @@ -305,13 +305,13 @@ func (w *walkCtx) walkRelation(tbl catalog.TableDescriptor) { }) } } - for _, c := range tbl.AllActiveAndInactiveUniqueWithoutIndexConstraints() { + for _, c := range tbl.UniqueConstraintsWithoutIndex() { w.walkUniqueWithoutIndexConstraint(tbl, c) } - for _, c := range tbl.AllActiveAndInactiveChecks() { + for _, c := range tbl.CheckConstraints() { w.walkCheckConstraint(tbl, c) } - for _, c := range tbl.AllActiveAndInactiveForeignKeys() { + for _, c := range tbl.OutboundForeignKeys() { w.walkForeignKeyConstraint(tbl, c) } @@ -319,10 +319,9 @@ func (w *walkCtx) walkRelation(tbl catalog.TableDescriptor) { w.backRefs.Add(dep.ID) return nil }) - _ = tbl.ForeachInboundFK(func(fk *descpb.ForeignKeyConstraint) error { - w.backRefs.Add(fk.OriginTableID) - return nil - }) + for _, fk := range tbl.InboundForeignKeys() { + w.backRefs.Add(fk.GetOriginTableID()) + } // Add a zone config element which is a stop gap to allow us to block // operations on tables. To minimize RTT impact limit // this to only tables and materialized views. @@ -574,53 +573,52 @@ func (w *walkCtx) walkIndex(tbl catalog.TableDescriptor, idx catalog.Index) { } func (w *walkCtx) walkUniqueWithoutIndexConstraint( - tbl catalog.TableDescriptor, c *descpb.UniqueWithoutIndexConstraint, + tbl catalog.TableDescriptor, c catalog.UniqueWithoutIndexConstraint, ) { // TODO(postamar): proper handling of constraint status + w.ev(scpb.Status_PUBLIC, &scpb.UniqueWithoutIndexConstraint{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, - ColumnIDs: catalog.MakeTableColSet(c.ColumnIDs...).Ordered(), + ConstraintID: c.GetConstraintID(), + ColumnIDs: c.CollectKeyColumnIDs().Ordered(), }) w.ev(scpb.Status_PUBLIC, &scpb.ConstraintName{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, - Name: c.Name, + ConstraintID: c.GetConstraintID(), + Name: c.GetName(), }) - if comment, ok, err := w.commentCache.GetConstraintComment(w.ctx, tbl.GetID(), c.ConstraintID); err == nil && ok { + if comment, ok, err := w.commentCache.GetConstraintComment(w.ctx, tbl.GetID(), c.GetConstraintID()); err == nil && ok { w.ev(scpb.Status_PUBLIC, &scpb.ConstraintComment{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, + ConstraintID: c.GetConstraintID(), Comment: comment, }) } } -func (w *walkCtx) walkCheckConstraint( - tbl catalog.TableDescriptor, c *descpb.TableDescriptor_CheckConstraint, -) { - expr, err := w.newExpression(c.Expr) +func (w *walkCtx) walkCheckConstraint(tbl catalog.TableDescriptor, c catalog.CheckConstraint) { + expr, err := w.newExpression(c.GetExpr()) if err != nil { panic(errors.NewAssertionErrorWithWrappedErrf(err, "check constraint %q in table %q (%d)", - c.Name, tbl.GetName(), tbl.GetID())) + c.GetName(), tbl.GetName(), tbl.GetID())) } // TODO(postamar): proper handling of constraint status w.ev(scpb.Status_PUBLIC, &scpb.CheckConstraint{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, - ColumnIDs: catalog.MakeTableColSet(c.ColumnIDs...).Ordered(), + ConstraintID: c.GetConstraintID(), + ColumnIDs: c.CollectReferencedColumnIDs().Ordered(), Expression: *expr, - FromHashShardedColumn: c.FromHashShardedColumn, + FromHashShardedColumn: c.IsHashShardingConstraint(), }) w.ev(scpb.Status_PUBLIC, &scpb.ConstraintName{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, - Name: c.Name, + ConstraintID: c.GetConstraintID(), + Name: c.GetName(), }) - if comment, ok, err := w.commentCache.GetConstraintComment(w.ctx, tbl.GetID(), c.ConstraintID); err == nil && ok { + if comment, ok, err := w.commentCache.GetConstraintComment(w.ctx, tbl.GetID(), c.GetConstraintID()); err == nil && ok { w.ev(scpb.Status_PUBLIC, &scpb.ConstraintComment{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, + ConstraintID: c.GetConstraintID(), Comment: comment, }) } else if err != nil { @@ -629,25 +627,25 @@ func (w *walkCtx) walkCheckConstraint( } func (w *walkCtx) walkForeignKeyConstraint( - tbl catalog.TableDescriptor, c *descpb.ForeignKeyConstraint, + tbl catalog.TableDescriptor, c catalog.ForeignKeyConstraint, ) { // TODO(postamar): proper handling of constraint status w.ev(scpb.Status_PUBLIC, &scpb.ForeignKeyConstraint{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, - ColumnIDs: catalog.MakeTableColSet(c.OriginColumnIDs...).Ordered(), - ReferencedTableID: c.ReferencedTableID, - ReferencedColumnIDs: catalog.MakeTableColSet(c.ReferencedColumnIDs...).Ordered(), + ConstraintID: c.GetConstraintID(), + ColumnIDs: c.ForeignKeyDesc().OriginColumnIDs, + ReferencedTableID: c.GetReferencedTableID(), + ReferencedColumnIDs: c.ForeignKeyDesc().ReferencedColumnIDs, }) w.ev(scpb.Status_PUBLIC, &scpb.ConstraintName{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, - Name: c.Name, + ConstraintID: c.GetConstraintID(), + Name: c.GetName(), }) - if comment, ok, err := w.commentCache.GetConstraintComment(w.ctx, tbl.GetID(), c.ConstraintID); err == nil && ok { + if comment, ok, err := w.commentCache.GetConstraintComment(w.ctx, tbl.GetID(), c.GetConstraintID()); err == nil && ok { w.ev(scpb.Status_PUBLIC, &scpb.ConstraintComment{ TableID: tbl.GetID(), - ConstraintID: c.ConstraintID, + ConstraintID: c.GetConstraintID(), Comment: comment, }) } else if err != nil { diff --git a/pkg/sql/schemachanger/scdeps/sctestdeps/test_deps.go b/pkg/sql/schemachanger/scdeps/sctestdeps/test_deps.go index 5651e43d955f..55064665af7b 100644 --- a/pkg/sql/schemachanger/scdeps/sctestdeps/test_deps.go +++ b/pkg/sql/schemachanger/scdeps/sctestdeps/test_deps.go @@ -986,7 +986,7 @@ func (s *TestState) Validator() scexec.Validator { func (s *TestState) ValidateCheckConstraint( ctx context.Context, tbl catalog.TableDescriptor, - constraint catalog.Constraint, + constraint catalog.CheckConstraint, override sessiondata.InternalExecutorOverride, ) error { s.LogSideEffectf("validate check constraint %v in table #%d", constraint.GetName(), tbl.GetID()) diff --git a/pkg/sql/schemachanger/scdeps/validator.go b/pkg/sql/schemachanger/scdeps/validator.go index ccd542fe1950..2db2b26b56ef 100644 --- a/pkg/sql/schemachanger/scdeps/validator.go +++ b/pkg/sql/schemachanger/scdeps/validator.go @@ -56,7 +56,7 @@ type ValidateInvertedIndexesFn func( type ValidateCheckConstraintFn func( ctx context.Context, tbl catalog.TableDescriptor, - constraint catalog.Constraint, + constraint catalog.CheckConstraint, sessionData *sessiondata.SessionData, runHistoricalTxn descs.HistoricalInternalExecTxnRunner, execOverride sessiondata.InternalExecutorOverride, @@ -114,7 +114,7 @@ func (vd validator) ValidateInvertedIndexes( func (vd validator) ValidateCheckConstraint( ctx context.Context, tbl catalog.TableDescriptor, - constraint catalog.Constraint, + constraint catalog.CheckConstraint, override sessiondata.InternalExecutorOverride, ) error { return vd.validateCheckConstraint(ctx, tbl, constraint, vd.newFakeSessionData(&vd.settings.SV), diff --git a/pkg/sql/schemachanger/scexec/dependencies.go b/pkg/sql/schemachanger/scexec/dependencies.go index 73439ea21266..36f8cb3d76cb 100644 --- a/pkg/sql/schemachanger/scexec/dependencies.go +++ b/pkg/sql/schemachanger/scexec/dependencies.go @@ -224,7 +224,7 @@ type Validator interface { ValidateCheckConstraint( ctx context.Context, tbl catalog.TableDescriptor, - constraint catalog.Constraint, + constraint catalog.CheckConstraint, override sessiondata.InternalExecutorOverride, ) error } diff --git a/pkg/sql/schemachanger/scexec/exec_validation.go b/pkg/sql/schemachanger/scexec/exec_validation.go index a686da23452e..c56782d7eb2a 100644 --- a/pkg/sql/schemachanger/scexec/exec_validation.go +++ b/pkg/sql/schemachanger/scexec/exec_validation.go @@ -69,7 +69,8 @@ func executeValidateCheckConstraint( if err != nil { return err } - if constraint.AsCheck() == nil { + check := constraint.AsCheck() + if check == nil { return errors.Newf("constraint ID %v does not identify a check constraint in table %v.", op.ConstraintID, op.TableID) } @@ -78,7 +79,7 @@ func executeValidateCheckConstraint( execOverride := sessiondata.InternalExecutorOverride{ User: username.RootUserName(), } - err = deps.Validator().ValidateCheckConstraint(ctx, table, constraint, execOverride) + err = deps.Validator().ValidateCheckConstraint(ctx, table, check, execOverride) if err != nil { return scerrors.SchemaChangerUserError(err) } diff --git a/pkg/sql/schemachanger/scexec/executor_external_test.go b/pkg/sql/schemachanger/scexec/executor_external_test.go index 78377a827cde..7303facabcd4 100644 --- a/pkg/sql/schemachanger/scexec/executor_external_test.go +++ b/pkg/sql/schemachanger/scexec/executor_external_test.go @@ -488,7 +488,7 @@ func (noopValidator) ValidateInvertedIndexes( func (noopValidator) ValidateCheckConstraint( ctx context.Context, tbl catalog.TableDescriptor, - constraint catalog.Constraint, + constraint catalog.CheckConstraint, override sessiondata.InternalExecutorOverride, ) error { return nil diff --git a/pkg/sql/schemachanger/scexec/scmutationexec/references.go b/pkg/sql/schemachanger/scexec/scmutationexec/references.go index ed2598ba024c..022a943287a8 100644 --- a/pkg/sql/schemachanger/scexec/scmutationexec/references.go +++ b/pkg/sql/schemachanger/scexec/scmutationexec/references.go @@ -196,9 +196,9 @@ func (m *visitor) RemoveForeignKeyBackReference( if err != nil { return err } - for _, fk := range tbl.AllActiveAndInactiveForeignKeys() { - if fk.ConstraintID == op.OriginConstraintID { - name = fk.Name + for _, fk := range tbl.OutboundForeignKeys() { + if fk.GetConstraintID() == op.OriginConstraintID { + name = fk.GetName() break } } @@ -355,8 +355,8 @@ func (m *visitor) UpdateBackReferencesInSequences( forwardRefs.Add(col.GetOwnsSequenceID(i)) } } else { - for _, c := range tbl.AllActiveAndInactiveChecks() { - ids, err := sequenceIDsInExpr(c.Expr) + for _, c := range tbl.CheckConstraints() { + ids, err := sequenceIDsInExpr(c.GetExpr()) if err != nil { return err } diff --git a/pkg/sql/scrub.go b/pkg/sql/scrub.go index f76898022928..1c8eccff6320 100644 --- a/pkg/sql/scrub.go +++ b/pkg/sql/scrub.go @@ -16,7 +16,6 @@ import ( "strings" "github.com/cockroachdb/cockroach/pkg/sql/catalog" - "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" @@ -412,53 +411,43 @@ func createConstraintCheckOperations( tableName *tree.TableName, asOf hlc.Timestamp, ) (results []checkOperation, err error) { - constraints, err := tableDesc.GetConstraintInfoWithLookup(func(id descpb.ID) (catalog.TableDescriptor, error) { - return p.Descriptors().GetImmutableTableByID(ctx, p.Txn(), id, tree.ObjectLookupFlagsWithRequired()) - }) - if err != nil { - return nil, err - } - + var constraints []catalog.Constraint // Keep only the constraints specified by the constraints in // constraintNames. if constraintNames != nil { - wantedConstraints := make(map[string]descpb.ConstraintDetail) for _, constraintName := range constraintNames { - if v, ok := constraints[string(constraintName)]; ok { - wantedConstraints[string(constraintName)] = v - } else { + c, _ := tableDesc.FindConstraintWithName(string(constraintName)) + if c == nil { return nil, pgerror.Newf(pgcode.UndefinedObject, "constraint %q of relation %q does not exist", constraintName, tableDesc.GetName()) } + constraints = append(constraints, c) } - constraints = wantedConstraints + } else { + constraints = tableDesc.EnforcedConstraints() } // Populate results with all constraints on the table. for _, constraint := range constraints { - switch constraint.Kind { - case descpb.ConstraintTypeCheck: - results = append(results, newSQLCheckConstraintCheckOperation( - tableName, - tableDesc, - constraint.CheckConstraint, - asOf, - )) - case descpb.ConstraintTypeFK: - results = append(results, newSQLForeignKeyCheckOperation( - tableName, - tableDesc, - constraint, - asOf, - )) - case descpb.ConstraintTypePK, descpb.ConstraintTypeUnique: - results = append(results, newSQLUniqueConstraintCheckOperation( - tableName, - tableDesc, - constraint, - asOf, - )) + var op checkOperation + if ck := constraint.AsCheck(); ck != nil { + op = newSQLCheckConstraintCheckOperation(tableName, tableDesc, ck, asOf) + } else if fk := constraint.AsForeignKey(); fk != nil { + referencedTable, err := p.Descriptors().GetImmutableTableByID( + ctx, p.Txn(), fk.GetReferencedTableID(), tree.ObjectLookupFlagsWithRequired(), + ) + if err != nil { + return nil, err + } + op = newSQLForeignKeyCheckOperation(tableName, tableDesc, fk, referencedTable, asOf) + } else if uwi := constraint.AsUniqueWithIndex(); uwi != nil { + op = newSQLUniqueWithIndexConstraintCheckOperation(tableName, tableDesc, uwi, asOf) + } else if uwoi := constraint.AsUniqueWithoutIndex(); uwoi != nil { + op = newSQLUniqueWithoutIndexConstraintCheckOperation(tableName, tableDesc, uwoi, asOf) + } else { + return nil, errors.AssertionFailedf("unknown constraint type %T", constraint) } + results = append(results, op) } return results, nil } diff --git a/pkg/sql/scrub_constraint.go b/pkg/sql/scrub_constraint.go index 72f46ff99cf2..6b162789f8a9 100644 --- a/pkg/sql/scrub_constraint.go +++ b/pkg/sql/scrub_constraint.go @@ -16,7 +16,6 @@ import ( "time" "github.com/cockroachdb/cockroach/pkg/sql/catalog" - "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/tabledesc" "github.com/cockroachdb/cockroach/pkg/sql/parser" "github.com/cockroachdb/cockroach/pkg/sql/scrub" @@ -29,7 +28,7 @@ import ( type sqlCheckConstraintCheckOperation struct { tableName *tree.TableName tableDesc catalog.TableDescriptor - checkDesc *descpb.TableDescriptor_CheckConstraint + check catalog.CheckConstraint asOf hlc.Timestamp // columns is a list of the columns returned in the query result @@ -53,13 +52,13 @@ type sqlCheckConstraintCheckRun struct { func newSQLCheckConstraintCheckOperation( tableName *tree.TableName, tableDesc catalog.TableDescriptor, - checkDesc *descpb.TableDescriptor_CheckConstraint, + check catalog.CheckConstraint, asOf hlc.Timestamp, ) *sqlCheckConstraintCheckOperation { return &sqlCheckConstraintCheckOperation{ tableName: tableName, tableDesc: tableDesc, - checkDesc: checkDesc, + check: check, asOf: asOf, } } @@ -69,7 +68,7 @@ func newSQLCheckConstraintCheckOperation( // then runs in the distSQL execution engine. func (o *sqlCheckConstraintCheckOperation) Start(params runParams) error { ctx := params.ctx - expr, err := parser.ParseExpr(o.checkDesc.Expr) + expr, err := parser.ParseExpr(o.check.GetExpr()) if err != nil { return err } @@ -134,7 +133,7 @@ func (o *sqlCheckConstraintCheckOperation) Next(params runParams) (tree.Datums, details := make(map[string]interface{}) rowDetails := make(map[string]interface{}) details["row_data"] = rowDetails - details["constraint_name"] = o.checkDesc.Name + details["constraint_name"] = o.check.GetName() for rowIdx, col := range o.columns { // TODO(joey): We should maybe try to get the underlying type. rowDetails[col.GetName()] = row[rowIdx].String() diff --git a/pkg/sql/scrub_fk.go b/pkg/sql/scrub_fk.go index 14e1ef4153f7..c84b30d9c04c 100644 --- a/pkg/sql/scrub_fk.go +++ b/pkg/sql/scrub_fk.go @@ -27,7 +27,7 @@ type sqlForeignKeyCheckOperation struct { tableName *tree.TableName tableDesc catalog.TableDescriptor referencedTableDesc catalog.TableDescriptor - constraint *descpb.ConstraintDetail + constraint catalog.ForeignKeyConstraint asOf hlc.Timestamp colIDToRowIdx catalog.TableColMap @@ -46,14 +46,15 @@ type sqlForeignKeyConstraintCheckRun struct { func newSQLForeignKeyCheckOperation( tableName *tree.TableName, tableDesc catalog.TableDescriptor, - constraint descpb.ConstraintDetail, + constraint catalog.ForeignKeyConstraint, + referencedTable catalog.TableDescriptor, asOf hlc.Timestamp, ) *sqlForeignKeyCheckOperation { return &sqlForeignKeyCheckOperation{ tableName: tableName, tableDesc: tableDesc, - constraint: &constraint, - referencedTableDesc: tabledesc.NewBuilder(constraint.ReferencedTable).BuildImmutableTable(), + constraint: constraint, + referencedTableDesc: referencedTable, asOf: asOf, } } @@ -66,7 +67,7 @@ func (o *sqlForeignKeyCheckOperation) Start(params runParams) error { checkQuery, _, err := nonMatchingRowQuery( o.tableDesc, - o.constraint.FK, + o.constraint.ForeignKeyDesc(), o.referencedTableDesc, false, /* limitResults */ ) @@ -82,12 +83,12 @@ func (o *sqlForeignKeyCheckOperation) Start(params runParams) error { } o.run.rows = rows - if len(o.constraint.FK.OriginColumnIDs) > 1 && o.constraint.FK.Match == descpb.ForeignKeyReference_FULL { + if o.constraint.NumOriginColumns() > 1 && o.constraint.Match() == descpb.ForeignKeyReference_FULL { // Check if there are any disallowed references where some columns are NULL // and some aren't. checkNullsQuery, _, err := matchFullUnacceptableKeyQuery( o.tableDesc, - o.constraint.FK, + o.constraint.ForeignKeyDesc(), false, /* limitResults */ ) if err != nil { @@ -103,18 +104,11 @@ func (o *sqlForeignKeyCheckOperation) Start(params runParams) error { } // Get primary key columns not included in the FK. - var colIDs []descpb.ColumnID - colIDs = append(colIDs, o.constraint.FK.OriginColumnIDs...) + originColIDs := o.constraint.CollectOriginColumnIDs() + colIDs := append(descpb.ColumnIDs(nil), o.constraint.ForeignKeyDesc().OriginColumnIDs...) for i := 0; i < o.tableDesc.GetPrimaryIndex().NumKeyColumns(); i++ { pkColID := o.tableDesc.GetPrimaryIndex().GetKeyColumnID(i) - found := false - for _, id := range o.constraint.FK.OriginColumnIDs { - if pkColID == id { - found = true - break - } - } - if !found { + if !originColIDs.Contains(pkColID) { colIDs = append(colIDs, pkColID) } } @@ -135,7 +129,7 @@ func (o *sqlForeignKeyCheckOperation) Next(params runParams) (tree.Datums, error details := make(map[string]interface{}) rowDetails := make(map[string]interface{}) details["row_data"] = rowDetails - details["constraint_name"] = o.constraint.FK.Name + details["constraint_name"] = o.constraint.GetName() // Collect the primary index values for generating the primary key // pretty string. @@ -148,7 +142,8 @@ func (o *sqlForeignKeyCheckOperation) Next(params runParams) (tree.Datums, error // Collect all of the values fetched from the index to generate a // pretty JSON dictionary for row_data. - for _, id := range o.constraint.FK.OriginColumnIDs { + for i, n := 0, o.constraint.NumOriginColumns(); i < n; i++ { + id := o.constraint.GetOriginColumnID(i) idx := o.colIDToRowIdx.GetDefault(id) col, err := tabledesc.FindPublicColumnWithID(o.tableDesc, id) if err != nil { @@ -156,16 +151,10 @@ func (o *sqlForeignKeyCheckOperation) Next(params runParams) (tree.Datums, error } rowDetails[col.GetName()] = row[idx].String() } + originColumnIDs := o.constraint.CollectOriginColumnIDs() for i := 0; i < o.tableDesc.GetPrimaryIndex().NumKeyColumns(); i++ { id := o.tableDesc.GetPrimaryIndex().GetKeyColumnID(i) - found := false - for _, fkID := range o.constraint.FK.OriginColumnIDs { - if id == fkID { - found = true - break - } - } - if !found { + if !originColumnIDs.Contains(id) { idx := o.colIDToRowIdx.GetDefault(id) col, err := tabledesc.FindPublicColumnWithID(o.tableDesc, id) if err != nil { diff --git a/pkg/sql/scrub_unique_constraint.go b/pkg/sql/scrub_unique_constraint.go index bdcb4c2a949e..74d7279bc64f 100644 --- a/pkg/sql/scrub_unique_constraint.go +++ b/pkg/sql/scrub_unique_constraint.go @@ -17,7 +17,6 @@ import ( "time" "github.com/cockroachdb/cockroach/pkg/sql/catalog" - "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/scrub" "github.com/cockroachdb/cockroach/pkg/sql/sem/catid" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" @@ -29,7 +28,7 @@ import ( type sqlUniqueConstraintCheckOperation struct { tableName *tree.TableName tableDesc catalog.TableDescriptor - constraint *descpb.ConstraintDetail + constraint catalog.UniqueConstraint cols []catid.ColumnID name string asOf hlc.Timestamp @@ -45,31 +44,43 @@ type sqlUniqueConstraintCheckOperation struct { run sqlCheckConstraintCheckRun } -func newSQLUniqueConstraintCheckOperation( +func newSQLUniqueWithIndexConstraintCheckOperation( tableName *tree.TableName, tableDesc catalog.TableDescriptor, - constraint descpb.ConstraintDetail, + constraint catalog.UniqueWithIndexConstraint, asOf hlc.Timestamp, ) *sqlUniqueConstraintCheckOperation { op := sqlUniqueConstraintCheckOperation{ tableName: tableName, tableDesc: tableDesc, - constraint: &constraint, + constraint: constraint, asOf: asOf, + cols: constraint.IndexDesc().KeyColumnIDs, + name: constraint.GetName(), + predicate: constraint.GetPredicate(), } - if constraint.UniqueWithoutIndexConstraint != nil { - op.cols = constraint.UniqueWithoutIndexConstraint.ColumnIDs - op.name = constraint.UniqueWithoutIndexConstraint.Name - op.predicate = constraint.UniqueWithoutIndexConstraint.Predicate - } else if constraint.Index != nil { - op.cols = constraint.Index.KeyColumnIDs - // Partitioning columns are prepended to the index but are not part of the - // unique constraint, so we ignore them. - if constraint.Index.Partitioning.NumImplicitColumns > 0 { - op.cols = op.cols[constraint.Index.Partitioning.NumImplicitColumns:] - } - op.name = constraint.Index.Name - op.predicate = constraint.Index.Predicate + // Partitioning columns are prepended to the index but are not part of the + // unique constraint, so we ignore them. + if n := constraint.GetPartitioning().NumImplicitColumns(); n > 0 { + op.cols = op.cols[n:] + } + return &op +} + +func newSQLUniqueWithoutIndexConstraintCheckOperation( + tableName *tree.TableName, + tableDesc catalog.TableDescriptor, + constraint catalog.UniqueWithoutIndexConstraint, + asOf hlc.Timestamp, +) *sqlUniqueConstraintCheckOperation { + op := sqlUniqueConstraintCheckOperation{ + tableName: tableName, + tableDesc: tableDesc, + constraint: constraint, + asOf: asOf, + cols: constraint.UniqueWithoutIndexDesc().ColumnIDs, + name: constraint.GetName(), + predicate: constraint.GetPredicate(), } return &op } diff --git a/pkg/sql/show_create.go b/pkg/sql/show_create.go index c26f22a7e1e9..57138eb1df2e 100644 --- a/pkg/sql/show_create.go +++ b/pkg/sql/show_create.go @@ -111,10 +111,10 @@ func ShowCreateTable( // TODO (lucy): Possibly include FKs in the mutations list here, or else // exclude check mutations below, for consistency. if displayOptions.FKDisplayMode != OmitFKClausesFromCreate { - if err := desc.ForeachOutboundFK(func(fk *descpb.ForeignKeyConstraint) error { + for _, fk := range desc.OutboundForeignKeys() { fkCtx := tree.NewFmtCtx(tree.FmtSimple) fkCtx.WriteString(",\n\tCONSTRAINT ") - fkCtx.FormatNameP(&fk.Name) + fkCtx.FormatName(fk.GetName()) fkCtx.WriteString(" ") // Passing in EmptySearchPath causes the schema name to show up in the // constraint definition, which we need for `cockroach dump` output to be @@ -123,20 +123,17 @@ func ShowCreateTable( &fkCtx.Buffer, dbPrefix, desc, - fk, + fk.ForeignKeyDesc(), lCtx, sessiondata.EmptySearchPath, ); err != nil { if displayOptions.FKDisplayMode == OmitMissingFKClausesFromCreate { - return nil + continue } // When FKDisplayMode == IncludeFkClausesInCreate. - return err + return "", err } f.WriteString(fkCtx.String()) - return nil - }); err != nil { - return "", err } } for _, idx := range desc.PublicNonPrimaryIndexes() { diff --git a/pkg/sql/show_create_clauses.go b/pkg/sql/show_create_clauses.go index 8eaf8da13e91..b8b7e4cb38e8 100644 --- a/pkg/sql/show_create_clauses.go +++ b/pkg/sql/show_create_clauses.go @@ -402,20 +402,14 @@ func showComments( }) } - // Get all the constraints for the table and create a map by ID. - constraints, err := table.GetConstraintInfo() - if err != nil { - return err - } - constraintIDToConstraint := make(map[descpb.ConstraintID]string) - for constraintName, constraint := range constraints { - constraintIDToConstraint[constraint.ConstraintID] = constraintName - } for _, constraintComment := range tc.constraints { f.WriteString(";\n") - constraintName := constraintIDToConstraint[descpb.ConstraintID(constraintComment.subID)] + c, err := table.FindConstraintWithID(descpb.ConstraintID(constraintComment.subID)) + if err != nil { + return err + } f.FormatNode(&tree.CommentOnConstraint{ - Constraint: tree.Name(constraintName), + Constraint: tree.Name(c.GetName()), Table: tn.ToUnresolvedObjectName(), Comment: &constraintComment.comment, }) @@ -696,36 +690,36 @@ func showConstraintClause( sessionData *sessiondata.SessionData, f *tree.FmtCtx, ) error { - for _, e := range desc.AllActiveAndInactiveChecks() { - if e.FromHashShardedColumn && e.Validity != descpb.ConstraintValidity_Unvalidated { + for _, e := range desc.CheckConstraints() { + if e.IsHashShardingConstraint() && !e.IsConstraintUnvalidated() { continue } f.WriteString(",\n\t") - if len(e.Name) > 0 { + if len(e.GetName()) > 0 { f.WriteString("CONSTRAINT ") - formatQuoteNames(&f.Buffer, e.Name) + formatQuoteNames(&f.Buffer, e.GetName()) f.WriteString(" ") } f.WriteString("CHECK (") - expr, err := schemaexpr.FormatExprForDisplay(ctx, desc, e.Expr, semaCtx, sessionData, tree.FmtParsable) + expr, err := schemaexpr.FormatExprForDisplay(ctx, desc, e.GetExpr(), semaCtx, sessionData, tree.FmtParsable) if err != nil { return err } f.WriteString(expr) f.WriteString(")") - if e.Validity != descpb.ConstraintValidity_Validated { + if !e.IsConstraintValidated() { f.WriteString(" NOT VALID") } } - for _, c := range desc.AllActiveAndInactiveUniqueWithoutIndexConstraints() { + for _, c := range desc.UniqueConstraintsWithoutIndex() { f.WriteString(",\n\t") - if len(c.Name) > 0 { + if len(c.GetName()) > 0 { f.WriteString("CONSTRAINT ") - formatQuoteNames(&f.Buffer, c.Name) + formatQuoteNames(&f.Buffer, c.GetName()) f.WriteString(" ") } f.WriteString("UNIQUE WITHOUT INDEX (") - colNames, err := desc.NamesForColumnIDs(c.ColumnIDs) + colNames, err := desc.NamesForColumnIDs(c.CollectKeyColumnIDs().Ordered()) if err != nil { return err } @@ -733,13 +727,13 @@ func showConstraintClause( f.WriteString(")") if c.IsPartial() { f.WriteString(" WHERE ") - pred, err := schemaexpr.FormatExprForDisplay(ctx, desc, c.Predicate, semaCtx, sessionData, tree.FmtParsable) + pred, err := schemaexpr.FormatExprForDisplay(ctx, desc, c.GetPredicate(), semaCtx, sessionData, tree.FmtParsable) if err != nil { return err } f.WriteString(pred) } - if c.Validity != descpb.ConstraintValidity_Validated { + if !c.IsConstraintValidated() { f.WriteString(" NOT VALID") } } diff --git a/pkg/sql/show_create_table_test.go b/pkg/sql/show_create_table_test.go index e7621a012638..6beff87c3cc9 100644 --- a/pkg/sql/show_create_table_test.go +++ b/pkg/sql/show_create_table_test.go @@ -84,9 +84,9 @@ func TestShowCreateTableWithConstraintInvalidated(t *testing.T) { ) // Change the check constraint from the hash shared index to unvalidated. - for _, c := range mut.AllActiveAndInactiveChecks() { - if c.Name == "check_crdb_internal_y_shard_16" { - c.Validity = descpb.ConstraintValidity_Unvalidated + for _, c := range mut.CheckConstraints() { + if c.GetName() == "check_crdb_internal_y_shard_16" { + c.CheckDesc().Validity = descpb.ConstraintValidity_Unvalidated } } diff --git a/pkg/sql/table_test.go b/pkg/sql/table_test.go index f38d375aa3e3..be544208d02c 100644 --- a/pkg/sql/table_test.go +++ b/pkg/sql/table_test.go @@ -472,7 +472,7 @@ func TestSerializedUDTsInTableDescriptor(t *testing.T) { return desc.PublicColumns()[0].GetComputeExpr() } getCheck := func(desc catalog.TableDescriptor) string { - return desc.GetChecks()[0].Expr + return desc.EnforcedCheckConstraints()[0].GetExpr() } testdata := []struct { colSQL string diff --git a/pkg/sql/tests/hash_sharded_test.go b/pkg/sql/tests/hash_sharded_test.go index ae3efa232836..8b59bc079ec9 100644 --- a/pkg/sql/tests/hash_sharded_test.go +++ b/pkg/sql/tests/hash_sharded_test.go @@ -60,12 +60,12 @@ func verifyTableDescriptorState( // Note that this method call will fail if the shard column doesn't exist shardColID := getShardColumnID(t, tableDesc, shardedIndexName) foundCheckConstraint := false - for _, check := range tableDesc.AllActiveAndInactiveChecks() { - usesShard, err := tableDesc.CheckConstraintUsesColumn(check, shardColID) + for _, check := range tableDesc.CheckConstraints() { + usesShard, err := tableDesc.CheckConstraintUsesColumn(check.CheckDesc(), shardColID) if err != nil { t.Fatal(err) } - if usesShard && check.FromHashShardedColumn { + if usesShard && check.IsHashShardingConstraint() { foundCheckConstraint = true break } diff --git a/pkg/sql/truncate.go b/pkg/sql/truncate.go index e62ad91b90dd..57262a13f337 100644 --- a/pkg/sql/truncate.go +++ b/pkg/sql/truncate.go @@ -354,11 +354,11 @@ func checkTableForDisallowedMutationsWithTruncate(desc *tabledesc.Mutable) error "cannot perform TRUNCATE on %q which has a column (%q) being "+ "dropped which depends on another object", desc.GetName(), col.GetName()) } - } else if c := m.AsConstraint(); c != nil { + } else if c := m.AsConstraintWithoutIndex(); c != nil { var constraintType descpb.ConstraintToUpdate_ConstraintType if ck := c.AsCheck(); ck != nil { constraintType = descpb.ConstraintToUpdate_CHECK - if c.NotNullColumnID() != 0 { + if ck.IsNotNullColumnConstraint() { constraintType = descpb.ConstraintToUpdate_NOT_NULL } } else if c.AsForeignKey() != nil { diff --git a/pkg/sql/type_change.go b/pkg/sql/type_change.go index ca0acab3ceb3..ee5a576941e2 100644 --- a/pkg/sql/type_change.go +++ b/pkg/sql/type_change.go @@ -832,8 +832,8 @@ func (t *typeSchemaChanger) canRemoveEnumValue( } // Examine all check constraints. - for _, chk := range desc.AllActiveAndInactiveChecks() { - foundUsage, err := findUsagesOfEnumValue(chk.Expr, member, typeDesc.ID) + for _, chk := range desc.CheckConstraints() { + foundUsage, err := findUsagesOfEnumValue(chk.GetExpr(), member, typeDesc.ID) if err != nil { return err } diff --git a/pkg/upgrade/upgrades/fix_userfile_descriptor_corruption.go b/pkg/upgrade/upgrades/fix_userfile_descriptor_corruption.go index 6f16843f138f..f83fc5dafee0 100644 --- a/pkg/upgrade/upgrades/fix_userfile_descriptor_corruption.go +++ b/pkg/upgrade/upgrades/fix_userfile_descriptor_corruption.go @@ -139,21 +139,25 @@ func mutationsLookLikeuserfilePayloadCorruption( } mutation := tableDesc.AllMutations()[0] if mutation.Adding() && mutation.DeleteOnly() { - if constraintMutation := mutation.AsConstraint(); constraintMutation != nil { - if fkConstraint := constraintMutation.AsForeignKey(); fkConstraint != nil { - targetTableDesc, err := descriptors.GetImmutableTableByID(ctx, txn, fkConstraint.ReferencedTableID, tree.ObjectLookupFlags{ + if fkConstraint := mutation.AsForeignKey(); fkConstraint != nil { + targetTableDesc, err := descriptors.GetImmutableTableByID( + ctx, + txn, + fkConstraint.GetReferencedTableID(), + tree.ObjectLookupFlags{ CommonLookupFlags: tree.CommonLookupFlags{ IncludeOffline: true, IncludeDropped: true, }, - }) - if err != nil { - return false - } - if tableLooksLikeUserfileFileTable(targetTableDesc) { - return true - } + }, + ) + if err != nil { + return false } + if tableLooksLikeUserfileFileTable(targetTableDesc) { + return true + } + } } return false diff --git a/pkg/upgrade/upgrades/tenant_table_migration_test.go b/pkg/upgrade/upgrades/tenant_table_migration_test.go index 205117eed09a..630834f3d991 100644 --- a/pkg/upgrade/upgrades/tenant_table_migration_test.go +++ b/pkg/upgrade/upgrades/tenant_table_migration_test.go @@ -137,7 +137,7 @@ func getDeprecatedTenantsDescriptor() *descpb.TableDescriptor { NextIndexID: 2, Privileges: catpb.NewCustomSuperuserPrivilegeDescriptor(privilege.ReadWriteData, username.NodeUserName()), NextMutationID: 1, - NextConstraintID: 1, + NextConstraintID: 2, FormatVersion: descpb.InterleavedFormatVersion, } } From 56d404efde5f5bb922d8773b318ad0fbd7959c7b Mon Sep 17 00:00:00 2001 From: Andrei Matei Date: Mon, 28 Nov 2022 13:09:39 -0500 Subject: [PATCH 2/2] upgrades: remove upgrade granting CREATELOGIN role opt This patch removes a permanent upgrade that was granting the CREATELOGIN role option to users that had the CREATEROLE option. This upgrade was introduced in 20.2, meant to grant the then-new CREATELOGIN option to users created in 20.1 and prior that had the CREATEROLE option (CREATELOGIN was being split from CREATEROLE). This code stayed around as a startupmigration since it was introduced, even though it didn't make sense for it to stay around after 20.2. Technically, I think this startup migration should have had the flag `IncludedInBootstrap=v20.2`, since we don't want it to run for clusters created at or after 20.2; this migration is not idempotent in the general sense, since it potentially picks up new, unintended users when it runs. Since 22.2, this migration would fail to run on anything but an empty system.role_options table because it would attempt to put NULLs into a non-nullable column. This was all benign since the startupmigrations had protection in place, preventing them for running a 2nd time after a completed attempt. So, for upgraded cluster, the migration only when the first 20.2 node came up, and for new clusters it would be a no-op since the system.role_options table starts empty. This migration became problematic in #91627 when I've turned the startupmigrations into upgrades. These upgrades run once when upgrading to 23.1; I'm relying on their idempotence. Fixes #92230 Fixes #92371 Fixes #92569 Release note: None --- pkg/upgrade/upgrades/permanent_upgrades.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pkg/upgrade/upgrades/permanent_upgrades.go b/pkg/upgrade/upgrades/permanent_upgrades.go index 856d2a215bc7..33f6f64e6790 100644 --- a/pkg/upgrade/upgrades/permanent_upgrades.go +++ b/pkg/upgrade/upgrades/permanent_upgrades.go @@ -57,20 +57,6 @@ func addRootUser( ` _, err = deps.InternalExecutor.Exec( ctx, "addRootToAdminRole", nil /* txn */, upsertMembership, username.AdminRole, username.RootUser) - if err != nil { - return err - } - - // Add the CREATELOGIN option to roles that already have CREATEROLE. - const upsertCreateRoleStmt = ` - UPSERT INTO system.role_options (username, option, value) - SELECT username, 'CREATELOGIN', NULL - FROM system.role_options - WHERE option = 'CREATEROLE' - ` - _, err = deps.InternalExecutor.Exec( - ctx, "add CREATELOGIN where a role already has CREATEROLE", nil, /* txn */ - upsertCreateRoleStmt) return err }