vitessio/vitess · error

ErrCannotUpdateImmutableColumn

ErrCannotUpdateImmutableColumn

Error message

%w %+v: %v

What it means

For UPDATEs on _vt.vreplication, the planner forbids modifying the immutable `id` column while allowing all other columns. An assignment to `id` is rejected with ErrCannotUpdateImmutableColumn, echoing the column name and the full assignment expression.

Source

Thrown at go/vt/vtctl/workflow/vexec/query_planner.go:224

	}, nil
}

func (planner *VReplicationQueryPlanner) planUpdate(upd *sqlparser.Update) (*FixedQueryPlan, error) {
	if upd.OrderBy != nil || upd.Limit != nil {
		return nil, fmt.Errorf(
			"%w: UPDATE must not have explicit ordering (have: %v) or limit clauses (have: %v): %v",
			ErrUnsupportedQueryConstruct,
			upd.OrderBy,
			upd.Limit,
			sqlparser.String(upd),
		)
	}

	// For updates on the _vt.vreplication table, we ban updates to the `id`
	// column, and allow updates to all other columns.
	for _, expr := range upd.Exprs {
		if expr.Name.Name.EqualString("id") {
			return nil, fmt.Errorf(
				"%w %+v: %v",
				ErrCannotUpdateImmutableColumn,
				expr.Name.Name,
				sqlparser.String(expr),
			)
		}
	}

	upd.Where = addDefaultWheres(planner, upd.Where)

	buf := sqlparser.NewTrackedBuffer(nil)
	buf.Myprintf("%v", upd)

	return &FixedQueryPlan{
		ParsedQuery: buf.ParsedQuery(),
		workflow:    planner.workflow,
		tmc:         planner.tmc,
	}, nil

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove `id = ...` from the SET clause and update only mutable columns
  2. If copying rows, enumerate explicit non-id columns instead of SELECT *-style full assignments
  3. Identify the exact expression from the error message (`sqlparser.String(expr)`) and edit that statement

Example fix

// before
UPDATE _vt.vreplication SET id = 42, state = 'Running' WHERE id = 7
// after
UPDATE _vt.vreplication SET state = 'Running' WHERE id = 7
Defensive patterns

Strategy: validation

Validate before calling

if upd, ok := stmt.(*sqlparser.Update); ok {
    for _, expr := range upd.Exprs {
        if expr.Name.Name.EqualString("id") {
            return fmt.Errorf("cannot update immutable column id on _vt.vreplication")
        }
    }
}

Type guard

func updatableColumns(upd *sqlparser.Update) []string {
    var cols []string
    for _, expr := range upd.Exprs {
        if !expr.Name.Name.EqualString("id") {
            cols = append(cols, expr.Name.Name.String())
        }
    }
    return cols
}

Try / catch

plan, err := planner.PlanQuery(ctx, stmt)
if errors.Is(err, vexec.ErrCannotUpdateImmutableColumn) {
    return fmt.Errorf("strip id from SET clause and retry: %w", err)
}

Prevention

When it happens

Trigger: Calling PlanQuery with an UPDATE on _vt.vreplication whose SET list contains an assignment to the `id` column (upd.Exprs entry with Name.Name == "id").

Common situations: Hand-written maintenance SQL that accidentally includes id in the SET list; ORM or code generator emitting full-row updates including the primary key; bulk UPDATE statements assigning all columns from a source row.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/0bbda9f40c25680f. Report an issue: GitHub.