vitessio/vitess · error

VT13001

VT13001

Error message

no update expression for the target

What it means

VT13001 'no update expression for the target' is panicked in createUpdateOpWithTarget (go/vt/vtgate/planbuilder/operators/update.go:244), called from createUpdateWithInputOp. When planning an UPDATE against an explicit target table, the planner looks up the precomputed updList (SET assignments) for that target's TableSet; an empty list means no SET assignments were attributed to the target, so no update plan can be produced. This is an internal invariant violation: the statement parser should have guaranteed at least one expression per updated table.

Source

Thrown at go/vt/vtgate/planbuilder/operators/update.go:244

func errIfDependentColumnUpdated(ctx *plancontext.PlanningContext, upd *sqlparser.Update, ueMap map[semantics.TableSet]updList) {
	for _, ue := range upd.Exprs {
		for _, list := range ueMap {
			for _, dc := range list {
				for _, bvExpr := range dc.jc.LHSExprs {
					if ctx.SemTable.EqualsExprWithDeps(ue.Name, bvExpr.Expr) {
						panic(vterrors.VT12001(
							fmt.Sprintf("'%s' column referenced in update expression '%s' is itself updated", sqlparser.String(ue.Name), sqlparser.String(dc.jc.Original))))
					}
				}
			}
		}
	}
}

func createUpdateOpWithTarget(ctx *plancontext.PlanningContext, updStmt *sqlparser.Update, target semantics.TableSet, uList updList) dmlOp {
	if len(uList) == 0 {
		panic(vterrors.VT13001("no update expression for the target"))
	}

	ti, err := ctx.SemTable.TableInfoFor(target)
	if err != nil {
		panic(vterrors.VT13001(err.Error()))
	}
	vTbl := ti.GetVindexTable()
	tblName, err := ti.Name()
	if err != nil {
		panic(err)
	}

	var leftComp sqlparser.ValTuple
	if len(vTbl.PrimaryKey) > 0 {
		leftComp = make(sqlparser.ValTuple, 0, len(vTbl.PrimaryKey))
	}
	cols := make([]*sqlparser.ColName, 0, len(vTbl.PrimaryKey))
	for _, col := range vTbl.PrimaryKey {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Ensure the SET clause assigns at least one column of every table named in the UPDATE target (qualify columns explicitly, e.g. SET t1.a = ...)
  2. Verify table aliases in the statement match those used in the SET clause
  3. If the SET clause is correct, reduce the statement and file a Vitess bug — this is an internal planning error

Example fix

-- before (no assignment attributed to t2 target)
UPDATE t1, t2 SET t1.a = 1 WHERE t1.id = t2.id;
-- after (explicit per-table assignments)
UPDATE t1, t2 SET t1.a = 1, t2.b = 2 WHERE t1.id = t2.id;
Defensive patterns

Strategy: validation

Validate before calling

// ensure every table in the UPDATE target list has at least one SET assignment
for _, tbl := range updateTargets(stmt) { if !hasSetAssignmentFor(stmt, tbl) { return fmt.Errorf("no SET assignment for %s", tbl) } }

Try / catch

if err != nil && strings.Contains(err.Error(), "no update expression for the target") {
    return rewriteWithQualifiedSetColumns(ctx, stmt)
}

Prevention

When it happens

Trigger: createUpdateOpWithTarget(ctx, updStmt, target, uList) is invoked with len(uList) == 0 — i.e. the updList map built by prepareUpdateExpressionList contains no entries for the specific target TableSet of a targetted UPDATE (UPDATE tbl, ... SET ... where assignments map to a different table than the one being planned).

Common situations: Multi-table UPDATE statements whose SET clause only assigns columns of tables other than the resolved target; planner confusion over table aliases or qualified vs unqualified SET columns; planner regressions after upgrades.

Related errors


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