{"record":{"id":"7bd26baff5d445d0","repo":"vitessio/vitess","slug":"vt09015","errorCode":"VT09015","errorMessage":"VT09015","messagePattern":"VT09015","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/planbuilder/operators/delete.go","lineNumber":172,"sourceCode":"}\n\n// getFirstVindex returns the first Vindex, if available\nfunc getFirstVindex(vTbl *vindexes.BaseTable) vindexes.Vindex {\n\tif len(vTbl.ColumnVindexes) > 0 {\n\t\treturn vTbl.ColumnVindexes[0].Vindex\n\t}\n\treturn nil\n}\n\nfunc createDeleteOpWithTarget(ctx *plancontext.PlanningContext, target semantics.TableSet, ignore sqlparser.Ignore) dmlOp {\n\tti, err := ctx.SemTable.TableInfoFor(target)\n\tif err != nil {\n\t\tpanic(vterrors.VT13001(err.Error()))\n\t}\n\n\tvTbl := ti.GetVindexTable()\n\tif len(vTbl.PrimaryKey) == 0 {\n\t\tpanic(vterrors.VT09015())\n\t}\n\ttblName, err := ti.Name()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tleftComp := make(sqlparser.ValTuple, 0, len(vTbl.PrimaryKey))\n\tcols := make([]*sqlparser.ColName, 0, len(vTbl.PrimaryKey))\n\tfor _, col := range vTbl.PrimaryKey {\n\t\tcolName := sqlparser.NewColNameWithQualifier(col.String(), tblName)\n\t\tcols = append(cols, colName)\n\t\tleftComp = append(leftComp, colName)\n\t\tctx.SemTable.Recursive[colName] = target\n\t}\n\t// optimize for case when there is only single column on left hand side.\n\tvar lhs sqlparser.Expr = leftComp\n\tif len(leftComp) == 1 {\n\t\tlhs = leftComp[0]","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/delete.go#L154-L190","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Define a primary key (PRIMARY KEY in DDL and a corresponding primary vindex in the vschema) on the target table.","Ensure the primary sequence/vindex columns match the table's declared PRIMARY KEY so PrimaryKey is populated.","Restructure the DELETE to avoid delete-with-input planning (drop LIMIT, split multi-table deletes) if the table legitimately has no PK.","Review the vschema entry for the table and fix missing column_vindexes configuration."],"exampleFix":"// before (vschema)\n{\"tables\": {\"logs\": {}}}\n\n// after\n{\"tables\": {\"logs\": {\"column_vindexes\": [{\"column\": \"id\", \"name\": \"hash\"}], \"column_list_authoritative\": true}}}","handlingStrategy":"validation","validationCode":"-- run before enabling delete-with-input workloads\nSELECT table_name, constraint_name\nFROM information_schema.table_constraints\nWHERE table_schema = ? AND constraint_type = 'PRIMARY KEY' AND table_name = ?;","typeGuard":"func tableHasPrimaryKey(vTbl *vindexes.BaseTable) bool {\n    return vTbl != nil && len(vTbl.PrimaryKey) > 0\n}","tryCatchPattern":"if err := execDelete(sql); err != nil {\n    if strings.Contains(err.Error(), \"VT09015\") {\n        return fmt.Errorf(\"table needs a primary key for this DELETE: %w\", err)\n    }\n    return err\n}","preventionTips":["Require PRIMARY KEY on every table created in Vitess","Keep vschema column_vindexes in sync with DDL primary keys","Lint vschema in CI for tables lacking a primary vindex","Avoid LIMIT deletes on tables you cannot guarantee have PKs"],"tags":["vitess","planbuilder","delete","vt09015","primary-key"],"backgroundTag":"missing-primary-key","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}