code: func (w *worker) finishDDLJob(t *meta.Meta, job *model.Job) (err error) {
  	startTime := time.Now()
  	defer func() {
  		metrics.DDLWorkerHistogram.WithLabelValues(metrics.WorkerFinishDDLJob, job.Type.String(), metrics.RetLabel(err)).Observe(time.Since(startTime).Seconds())
  	}()
  
  	if !job.IsCancelled() {
  		switch job.Type {
  		case model.ActionAddIndex:
  			if job.State != model.JobStateRollbackDone {
  				break
  			}
  
  			// After rolling back an AddIndex operation, we need to use delete-range to delete the half-done index data.
  			err = w.deleteRange(job)
  		case model.ActionDropSchema, model.ActionDropTable, model.ActionTruncateTable, model.ActionDropIndex, model.ActionDropTablePartition, model.ActionTruncateTablePartition:
  			err = w.deleteRange(job)
  		}
  	}
  	switch job.Type {
  	case model.ActionRecoverTable:
  		err = finishRecoverTable(w, t, job)
  	}
  	if err != nil {
  		return errors.Trace(err)
  	}
  
  	_, err = t.DeQueueDDLJob()
  	if err != nil {
  		return errors.Trace(err)
  	}
  
  	job.BinlogInfo.FinishedTS = t.StartTS
  	logutil.Logger(w.logCtx).Info("[ddl] finish DDL job", zap.String("job", job.String()))
  	err = t.AddHistoryDDLJob(job)
  	return errors.Trace(err)
  }
docs: // finishDDLJob deletes the finished DDL job in the ddl queue and puts it to history queue.
  // If the DDL job need to handle in background, it will prepare a background job.