Repository: pingcap/tidb
File: pkg/planner/core/logical_plan_builder.go (Go)

Code before:
		// If the pruning happens, we project the columns not pruned as the final output of the below plan.
		finalProjCols = make([]*expression.Column, 0, oldLen/2)
		finalProjNames = make(types.NameSlice, 0, oldLen/2)
		for i, found := nonPruned.NextSet(0); found; i, found = nonPruned.NextSet(i + 1) {
			finalProjCols = append(finalProjCols, p.Schema().Columns[i])
			finalProjNames = append(finalProjNames, p.OutputNames()[i])
		}
	} else {
		// Otherwise, we just use the original schema.
		finalProjCols = make([]*expression.Column, oldLen)
		copy(finalProjCols, p.Schema().Columns[:oldLen])
		finalProjNames = preProjNames.Shallow()
	}
	proj := logicalop.LogicalProjection{Exprs: expression.Column2Exprs(finalProjCols)}.Init(b.ctx, b.getSelectOffset())
	proj.SetChildren(p)
	proj.SetSchema(expression.NewSchema(finalProjCols...))
	proj.SetOutputNames(finalProjNames)
	p = proj
	del.SetOutputNames(p.OutputNames())
	del.SelectPlan, _, err = DoOptimize(ctx, b.ctx, b.optFlag, p)

	return del, err
}

// buildDeleteAsUpdate builds an UPDATE plan from a DELETE statement for soft delete tables.
// This is called when SoftDeleteRewrite is enabled and all tables involved have soft delete enabled.
// The DELETE is rewritten to: UPDATE ... SET _tidb_softdelete_time = NOW()
func (b *PlanBuilder) buildDeleteAsUpdate(
	ctx context.Context,
	ds *ast.DeleteStmt,
	softdeleteTables []*resolve.TableNameW) (base.Plan, error) {
	b.pushSelectOffset(0)
	b.pushTableHints(ds.TableHints, 0)
	defer func() {
		b.popSelectOffset()
		b.popTableHints()
	}()

	p, oldSchemaLen, err := b.buildDMLSelectPlan(
		ctx,
		ds.With,
		ds.TableRefs.TableRefs,
		ds.Where,
		ds.Order,
		ds.Limit,
		!ds.IsMultiTable,
		true, // isUpdate (we're building an UPDATE plan)
	)
	if err != nil {
		return nil, err
	}

Review comment (suggestion):
The parameter name "softdeleteTables" has inconsistent casing. Earlier in the code (line 5910, 5962, 5963, 5991) the variable is named "softDeleteTables" (camelCase with capital "D"), but here it's named "softdeleteTables" (all lowercase "delete"). For consistency, this should be "softDeleteTables".

Code after:
			finalProjNames = append(finalProjNames, p.OutputNames()[i])
		}
	} else {
		// Otherwise, we just use the original schema.
		finalProjCols = make([]*expression.Column, oldLen)
		copy(finalProjCols, p.Schema().Columns[:oldLen])
		finalProjNames = preProjNames.Shallow()
	}
	proj := logicalop.LogicalProjection{Exprs: expression.Column2Exprs(finalProjCols)}.Init(b.ctx, b.getSelectOffset())
	proj.SetChildren(p)
	proj.SetSchema(expression.NewSchema(finalProjCols...))
	proj.SetOutputNames(finalProjNames)
	p = proj
	del.SetOutputNames(p.OutputNames())
	del.SelectPlan, _, err = DoOptimize(ctx, b.ctx, b.optFlag, p)

	return del, err
}

// buildSoftdeleteAsUpdate builds an UPDATE plan from a DELETE statement for soft delete tables.
// This is called when SoftDeleteRewrite is enabled and all tables involved have soft delete enabled.
// The DELETE is rewritten to: UPDATE ... SET _tidb_softdelete_time = NOW(6)
func (b *PlanBuilder) buildSoftdeleteAsUpdate(
	ctx context.Context,
	ds *ast.DeleteStmt,
	softdeleteTables []*resolve.TableNameW) (base.Plan, error) {
	b.pushSelectOffset(0)
	b.pushTableHints(ds.TableHints, 0)
	defer func() {
		b.popSelectOffset()
		b.popTableHints()
	}()

	p, oldSchemaLen, err := b.buildDMLSelectPlan(
		ctx,
		ds.With,
		ds.TableRefs.TableRefs,
		ds.Where,
		ds.Order,
		ds.Limit,
		!ds.IsMultiTable,
		true, // isUpdate (we're building an UPDATE plan)
	)
	if err != nil {
		return nil, err
	}

	// Generate assignment list for soft delete: SET _tidb_softdelete_time = NOW(6)
	assignList := make([]*ast.Assignment, 0, len(softdeleteTables))
	tableList := make([]*ast.TableName, 0, len(softdeleteTables))
	for _, tbl := range softdeleteTables {