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

Code before:
		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 documentation comment on line 6101 states the DELETE is rewritten to "UPDATE ... SET _tidb_softdelete_time = NOW()" but the actual implementation on line 6141 uses "NOW(6)" with a precision argument. The documentation should be updated to reflect the actual implementation: "UPDATE ... SET _tidb_softdelete_time = NOW(6)".
```suggestion
// The DELETE is rewritten to: UPDATE ... SET _tidb_softdelete_time = NOW(6)
```

Code after:
		}
	} 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 {
		assignList = append(assignList, &ast.Assignment{