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
- 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 = ...)
- Verify table aliases in the statement match those used in the SET clause
- 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
- Qualify SET columns with the table alias (SET t1.a = ...) in multi-table updates
- Include at least one assignment per table named in the UPDATE
- Keep aliases consistent between the target list and the SET clause
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.