github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/backupccl/restore_schema_change_creation.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package backupccl
    10  
    11  import (
    12  	"context"
    13  	"fmt"
    14  	"strings"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/jobs"
    17  	"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
    18  	"github.com/cockroachdb/cockroach/pkg/keys"
    19  	"github.com/cockroachdb/cockroach/pkg/kv"
    20  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    22  	"github.com/cockroachdb/cockroach/pkg/util/log"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  // jobDescriptionFromMutationID returns a string description of a mutation with
    27  // a particular ID on a given tableDesc, as well as the number of mutations
    28  // associated with it. This is only used to reconstruct a job based off a
    29  // mutation, namely during RESTORE.
    30  // N.B.: This is only to get an indication of what the schema change was trying
    31  // to do and is not meant to be the exact same description/SQL that was used in
    32  // the original job.
    33  func jobDescriptionFromMutationID(
    34  	tableDesc *sqlbase.TableDescriptor, id sqlbase.MutationID,
    35  ) (string, int, error) {
    36  	var jobDescBuilder strings.Builder
    37  	mutationCount := 0
    38  	for _, m := range tableDesc.Mutations {
    39  		if m.MutationID == id {
    40  			mutationCount++
    41  			// This is one of the mutations that we're looking for.
    42  			// Note that for primary key swaps, we want the last mutation in this list.
    43  			isPrimaryKeySwap := false
    44  			switch m.Descriptor_.(type) {
    45  			case *sqlbase.DescriptorMutation_PrimaryKeySwap:
    46  				isPrimaryKeySwap = true
    47  			}
    48  
    49  			if isPrimaryKeySwap {
    50  				// Primary key swaps have multiple mutations with the same ID, but we
    51  				// can derive the description from just the PrimaryKeySwap mutation
    52  				// which appears after the other mutations.
    53  				jobDescBuilder.Reset()
    54  			} else if jobDescBuilder.Len() != 0 {
    55  				jobDescBuilder.WriteString("; ")
    56  			}
    57  
    58  			if m.Rollback {
    59  				jobDescBuilder.WriteString("rollback for ")
    60  			}
    61  
    62  			jobDescBuilder.WriteString(fmt.Sprintf("schema change on %s ", tableDesc.Name))
    63  
    64  			if !isPrimaryKeySwap {
    65  				switch m.Direction {
    66  				case sqlbase.DescriptorMutation_ADD:
    67  					jobDescBuilder.WriteString("adding ")
    68  				case sqlbase.DescriptorMutation_DROP:
    69  					jobDescBuilder.WriteString("dropping ")
    70  				default:
    71  					return "", 0, errors.Newf("unsupported mutation %+v, while restoring table %+v", m, tableDesc)
    72  				}
    73  			}
    74  
    75  			switch t := m.Descriptor_.(type) {
    76  			case *sqlbase.DescriptorMutation_Column:
    77  				jobDescBuilder.WriteString("column ")
    78  				jobDescBuilder.WriteString(t.Column.Name)
    79  				if m.Direction == sqlbase.DescriptorMutation_ADD {
    80  					jobDescBuilder.WriteString(" " + t.Column.Type.String())
    81  				}
    82  			case *sqlbase.DescriptorMutation_Index:
    83  				jobDescBuilder.WriteString("index ")
    84  				jobDescBuilder.WriteString(t.Index.Name + " for " + tableDesc.Name + " (")
    85  				jobDescBuilder.WriteString(strings.Join(t.Index.ColumnNames, ", "))
    86  				jobDescBuilder.WriteString(")")
    87  			case *sqlbase.DescriptorMutation_Constraint:
    88  				jobDescBuilder.WriteString("constraint ")
    89  				jobDescBuilder.WriteString(t.Constraint.Name)
    90  			case *sqlbase.DescriptorMutation_PrimaryKeySwap:
    91  				jobDescBuilder.WriteString("changing primary key to (")
    92  				newIndexID := t.PrimaryKeySwap.NewPrimaryIndexId
    93  				// Find the ADD INDEX mutation with the same mutation ID that is adding
    94  				// the new index.
    95  				for _, otherMut := range tableDesc.Mutations {
    96  					if indexMut, ok := otherMut.Descriptor_.(*sqlbase.DescriptorMutation_Index); ok &&
    97  						indexMut.Index.ID == newIndexID &&
    98  						otherMut.MutationID == m.MutationID &&
    99  						m.Direction == sqlbase.DescriptorMutation_ADD {
   100  						jobDescBuilder.WriteString(strings.Join(indexMut.Index.ColumnNames, ", "))
   101  					}
   102  				}
   103  				jobDescBuilder.WriteString(")")
   104  			default:
   105  				return "", 0, errors.Newf("unsupported mutation %+v, while restoring table %+v", m, tableDesc)
   106  			}
   107  		}
   108  	}
   109  
   110  	jobDesc := jobDescBuilder.String()
   111  	if mutationCount == 0 {
   112  		return "", 0, errors.Newf("could not find mutation %d on table %s (%d) while restoring", id, tableDesc.Name, tableDesc.ID)
   113  	}
   114  	return jobDesc, mutationCount, nil
   115  }
   116  
   117  // createSchemaChangeJobsFromMutations creates and runs jobs for any mutations
   118  // on the table descriptor. It also updates tableDesc's MutationJobs to
   119  // reference the new jobs. This is only used to reconstruct a job based off a
   120  // mutation, namely during RESTORE.
   121  func createSchemaChangeJobsFromMutations(
   122  	ctx context.Context,
   123  	jr *jobs.Registry,
   124  	codec keys.SQLCodec,
   125  	txn *kv.Txn,
   126  	username string,
   127  	tableDesc *sqlbase.TableDescriptor,
   128  ) ([]*jobs.StartableJob, error) {
   129  	mutationJobs := make([]sqlbase.TableDescriptor_MutationJob, 0, len(tableDesc.MutationJobs))
   130  	newJobs := make([]*jobs.StartableJob, 0, len(tableDesc.MutationJobs))
   131  	for _, mj := range tableDesc.MutationJobs {
   132  		mutationID := mj.MutationID
   133  		jobDesc, mutationCount, err := jobDescriptionFromMutationID(tableDesc, mj.MutationID)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  		spanList := make([]jobspb.ResumeSpanList, mutationCount)
   138  		for i := range spanList {
   139  			spanList[i] = jobspb.ResumeSpanList{ResumeSpans: []roachpb.Span{tableDesc.PrimaryIndexSpan(codec)}}
   140  		}
   141  		jobRecord := jobs.Record{
   142  			// We indicate that this schema change was triggered by a RESTORE since
   143  			// the job description may not have all the information to fully describe
   144  			// the schema change.
   145  			Description:   "RESTORING: " + jobDesc,
   146  			Username:      username,
   147  			DescriptorIDs: sqlbase.IDs{tableDesc.GetID()},
   148  			Details: jobspb.SchemaChangeDetails{
   149  				TableID:        tableDesc.ID,
   150  				MutationID:     mutationID,
   151  				ResumeSpanList: spanList,
   152  				FormatVersion:  jobspb.JobResumerFormatVersion,
   153  			},
   154  			Progress: jobspb.SchemaChangeProgress{},
   155  		}
   156  		newJob, err := jr.CreateStartableJobWithTxn(ctx, jobRecord, txn, nil)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		newMutationJob := sqlbase.TableDescriptor_MutationJob{
   161  			MutationID: mutationID,
   162  			JobID:      *newJob.ID(),
   163  		}
   164  		mutationJobs = append(mutationJobs, newMutationJob)
   165  		newJobs = append(newJobs, newJob)
   166  
   167  		log.Infof(ctx, "queued new schema change job %d for table %d, mutation %d",
   168  			newJob.ID(), tableDesc.ID, mutationID)
   169  	}
   170  	tableDesc.MutationJobs = mutationJobs
   171  	return newJobs, nil
   172  }