vitessio/vitess · error

VT09015

VT09015

Error message

VT09015

What it means

VT09015 is raised when the table targeted by a delete-with-input planned DELETE has no primary key defined in its vindex table metadata. Delete-with-input planning builds a `DELETE ... WHERE pk IN (list of values)` statement from the selected rows, which requires a primary key to uniquely identify rows to delete. Without one, the planner cannot construct the delete and aborts.

Source

Thrown at go/vt/vtgate/planbuilder/operators/delete.go:172

}

// getFirstVindex returns the first Vindex, if available
func getFirstVindex(vTbl *vindexes.BaseTable) vindexes.Vindex {
	if len(vTbl.ColumnVindexes) > 0 {
		return vTbl.ColumnVindexes[0].Vindex
	}
	return nil
}

func createDeleteOpWithTarget(ctx *plancontext.PlanningContext, target semantics.TableSet, ignore sqlparser.Ignore) dmlOp {
	ti, err := ctx.SemTable.TableInfoFor(target)
	if err != nil {
		panic(vterrors.VT13001(err.Error()))
	}

	vTbl := ti.GetVindexTable()
	if len(vTbl.PrimaryKey) == 0 {
		panic(vterrors.VT09015())
	}
	tblName, err := ti.Name()
	if err != nil {
		panic(err)
	}

	leftComp := make(sqlparser.ValTuple, 0, len(vTbl.PrimaryKey))
	cols := make([]*sqlparser.ColName, 0, len(vTbl.PrimaryKey))
	for _, col := range vTbl.PrimaryKey {
		colName := sqlparser.NewColNameWithQualifier(col.String(), tblName)
		cols = append(cols, colName)
		leftComp = append(leftComp, colName)
		ctx.SemTable.Recursive[colName] = target
	}
	// optimize for case when there is only single column on left hand side.
	var lhs sqlparser.Expr = leftComp
	if len(leftComp) == 1 {
		lhs = leftComp[0]

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Define a primary key (PRIMARY KEY in DDL and a corresponding primary vindex in the vschema) on the target table.
  2. Ensure the primary sequence/vindex columns match the table's declared PRIMARY KEY so PrimaryKey is populated.
  3. Restructure the DELETE to avoid delete-with-input planning (drop LIMIT, split multi-table deletes) if the table legitimately has no PK.
  4. Review the vschema entry for the table and fix missing column_vindexes configuration.

Example fix

// before (vschema)
{"tables": {"logs": {}}}

// after
{"tables": {"logs": {"column_vindexes": [{"column": "id", "name": "hash"}], "column_list_authoritative": true}}}
Defensive patterns

Strategy: validation

Validate before calling

-- run before enabling delete-with-input workloads
SELECT table_name, constraint_name
FROM information_schema.table_constraints
WHERE table_schema = ? AND constraint_type = 'PRIMARY KEY' AND table_name = ?;

Type guard

func tableHasPrimaryKey(vTbl *vindexes.BaseTable) bool {
    return vTbl != nil && len(vTbl.PrimaryKey) > 0
}

Try / catch

if err := execDelete(sql); err != nil {
    if strings.Contains(err.Error(), "VT09015") {
        return fmt.Errorf("table needs a primary key for this DELETE: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: DELETE (with LIMIT, multi-table targets, or FK children) against a table whose vindexes/BaseTable metadata yields an empty PrimaryKey list — e.g. a table defined in the vschema without a primary vindex that also serves as a primary key, on a sharded keyspace.

Common situations: Vschema authoring mistakes: table declared without `column_vindexes` covering a primary key, or a non-MySQL-style table without PRIMARY KEY; deleting from such a table with a LIMIT clause forcing delete-with-input planning.

Related errors


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