vitessio/vitess · error · VitessError

VT09003

VT09003

Error message

VT09003: INSERT query does not have primary vindex column '%v' in the column list

What it means

VT09003 is raised by insertSelectPlan in go/vt/vtgate/planbuilder/operators/insert.go:451 when an `INSERT ... SELECT` does not include the primary vindex column in its column list. Vitess must know the primary vindex value to route each row, so the first column vindex's columns must be present in the insert columns.

Source

Thrown at go/vt/vtgate/planbuilder/operators/insert.go:451

	insertTbl := insOp.tableTarget()
	selTables := TablesUsed(selOp)
	if slices.Contains(selTables, insertTbl) {
		insertSelect.ForceNonStreaming = true
	}

	if len(insOp.ColVindexes) == 0 {
		return insertSelect
	}

	colVindexes := insOp.ColVindexes
	vv := make([][]int, len(colVindexes))
	for idx, colVindex := range colVindexes {
		for _, col := range colVindex.Columns {
			checkAndErrIfVindexChanging(sqlparser.UpdateExprs(ins.OnDup), col)
			colNum := findColumn(ins, col)
			// sharding column values should be provided in the insert.
			if colNum == -1 && idx == 0 {
				panic(vterrors.VT09003(col))
			}
			vv[idx] = append(vv[idx], colNum)
		}
	}
	insOp.VindexValueOffset = vv
	return insertSelect
}

func columnMismatch(gen *Generate, ins *sqlparser.Insert, sel sqlparser.TableStatement) bool {
	origColCount := len(ins.Columns)
	if gen != nil && gen.added {
		// One column got added to the insert query ast for auto increment column.
		// adjusting it here for comparison.
		origColCount--
	}
	if origColCount < sel.GetColumnCount() {
		return true
	}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Add the primary vindex column to the INSERT column list and supply its value in the SELECT
  2. If the column is auto-increment, omit it from the column list entirely so modifyForAutoinc handles generation
  3. Confirm which column(s) form the first (primary) column vindex in the vschema

Example fix

// before
INSERT INTO t (name) SELECT name FROM other;
// after
INSERT INTO t (id, name) SELECT other_id, name FROM other;
Defensive patterns

Strategy: validation

Validate before calling

// Check the primary vindex column is present in the INSERT column list
const primaryVindexCols = vschema.tables['t'].column_vindexes[0].columns;
const missing = primaryVindexCols.filter(c => !insertCols.includes(c));
if (missing.length) throw new Error('primary vindex columns missing from INSERT: ' + missing.join(','));

Type guard

function hasPrimaryVindexColumns(insertCols, vindexes) {
  const primary = vindexes[0].columns || [vindexes[0].column];
  return primary.every(c => insertCols.map(x => x.toLowerCase()).includes(c.toLowerCase()));
}

Try / catch

try {
  await vtgate.execute(sql, args);
} catch (e) {
  if (String(e).includes('VT09003')) {
    throw new Error('Add the primary vindex column to the INSERT column list and supply it in the SELECT');
  }
  throw e;
}

Prevention

When it happens

Trigger: INSERT INTO t (a, b) SELECT ... where t's primary vindex column is `id` and `id` is missing from the column list (findColumn returns -1 for idx == 0).

Common situations: Assuming Vitess will auto-generate the sharding/primary key in INSERT..SELECT; copying column lists from single-shard MySQL code to sharded Vitess tables; vschema primary vindex changed to a different column.

Related errors


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