func isSchemaChangeRetryError(err error) bool { switch err { case sqlbase.ErrDescriptorNotFound: return false default: return !sqlbase.IsIntegrityConstraintError(err) } }
// Execute the entire schema change in steps. func (sc SchemaChanger) exec() error { // Acquire lease. lease, err := sc.AcquireLease() if err != nil { return err } needRelease := true // Always try to release lease. defer func(l *sqlbase.TableDescriptor_SchemaChangeLease) { // If the schema changer deleted the descriptor, there's no longer a lease to be // released. if !needRelease { return } if err := sc.ReleaseLease(*l); err != nil { log.Warning(context.TODO(), err) } }(&lease) // Increment the version and unset tableDescriptor.UpVersion. desc, err := sc.MaybeIncrementVersion() if err != nil { return err } table := desc.GetTable() if table.Dropped() { lease, err = sc.ExtendLease(lease) if err != nil { return err } // Wait for everybody to see the version with the deleted bit set. When // this returns, nobody has any leases on the table, nor can get new leases, // so the table will no longer be modified. if err := sc.waitToUpdateLeases(sc.tableID); err != nil { return err } // Truncate the table and delete the descriptor. if err := sc.truncateAndDropTable(context.TODO(), &lease, table); err != nil { return err } needRelease = false return nil } if table.Adding() { for _, idx := range table.AllNonDropIndexes() { if idx.ForeignKey.IsSet() { if err := sc.waitToUpdateLeases(idx.ForeignKey.Table); err != nil { return err } } } if _, err := sc.leaseMgr.Publish( table.ID, func(tbl *sqlbase.TableDescriptor) error { tbl.State = sqlbase.TableDescriptor_PUBLIC return nil }, func(txn *client.Txn) error { return nil }, ); err != nil { return err } } if table.Renamed() { lease, err = sc.ExtendLease(lease) if err != nil { return err } // Wait for everyone to see the version with the new name. When this // returns, no new transactions will be using the old name for the table, so // the old name can now be re-used (by CREATE). if err := sc.waitToUpdateLeases(sc.tableID); err != nil { return err } if sc.testingKnobs.RenameOldNameNotInUseNotification != nil { sc.testingKnobs.RenameOldNameNotInUseNotification() } // Free up the old name(s). err := sc.db.Txn(context.TODO(), func(txn *client.Txn) error { b := txn.NewBatch() for _, renameDetails := range table.Renames { tbKey := tableKey{renameDetails.OldParentID, renameDetails.OldName}.Key() b.Del(tbKey) } return txn.Run(b) }) if err != nil { return err } // Clean up - clear the descriptor's state. _, err = sc.leaseMgr.Publish(sc.tableID, func(desc *sqlbase.TableDescriptor) error { desc.Renames = nil return nil }, nil) if err != nil { return err } } // Wait for the schema change to propagate to all nodes after this function // returns, so that the new schema is live everywhere. This is not needed for // correctness but is done to make the UI experience/tests predictable. defer func() { if err := sc.waitToUpdateLeases(sc.tableID); err != nil { log.Warning(context.TODO(), err) } }() if sc.mutationID == sqlbase.InvalidMutationID { // Nothing more to do. return nil } // Another transaction might set the up_version bit again, // but we're no longer responsible for taking care of that. // Run through mutation state machine and backfill. err = sc.runStateMachineAndBackfill(&lease) // Purge the mutations if the application of the mutations failed due to // an integrity constraint violation. All other errors are transient // errors that are resolved by retrying the backfill. if sqlbase.IsIntegrityConstraintError(err) { log.Warningf(context.TODO(), "reversing schema change due to irrecoverable error: %s", err) if errReverse := sc.reverseMutations(err); errReverse != nil { // Although the backfill did hit an integrity constraint violation // and made a decision to reverse the mutations, // reverseMutations() failed. If exec() is called again the entire // schema change will be retried. return errReverse } // After this point the schema change has been reversed and any retry // of the schema change will act upon the reversed schema change. if errPurge := sc.runStateMachineAndBackfill(&lease); errPurge != nil { // Don't return this error because we do want the caller to know // that an integrity constraint was violated with the original // schema change. The reversed schema change will be // retried via the async schema change manager. log.Warningf(context.TODO(), "error purging mutation: %s, after error: %s", errPurge, err) } } return err }