vitessio/vitess · error · VitessError

VT09017

VT09017

Error message

VT09017: INSERT with a target destination is not allowed

What it means

VT09017 is raised by createInsertOperator in go/vt/vtgate/planbuilder/operators/insert.go:363 when an INSERT statement targets a explicitly-set destination (a targeted routing) instead of a keyspace/table resolved through normal routing. Vitess does not support combining a route target (e.g. from USE statements with @primary/@replica or vtgate target syntax) with INSERT planning, so it rejects the statement.

Source

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

		}
		var def sqlparser.Expr
		idx := ins.Columns.FindColumn(col.Name)
		if idx == -1 {
			def = findDefault(vTbl, col.Name)
			// no default, replace it with null value.
			if def == nil {
				def = &sqlparser.NullVal{}
			}
		}
		offsets = append(offsets, uComp{idx, def})
		return false, nil
	}, expr)
	return offsets, false
}

func createInsertOperator(ctx *plancontext.PlanningContext, insStmt *sqlparser.Insert, vTbl *vindexes.BaseTable, routing Routing) (op Operator) {
	if _, target := routing.(*TargetedRouting); target {
		panic(vterrors.VT09017("INSERT with a target destination is not allowed"))
	}

	insOp := &Insert{
		VTable: vTbl,
		AST:    insStmt,
	}
	route := &Route{
		unaryOperator: newUnaryOp(insOp),
		Routing:       routing,
	}

	// Table column list is nil then add all the columns
	// If the column list is empty then add only the auto-inc column and
	// this happens on calling modifyForAutoinc
	if insStmt.Columns == nil && valuesProvided(insStmt.Rows) {
		if vTbl.ColumnListAuthoritative {
			insStmt = populateInsertColumnlist(insStmt, vTbl)
		} else {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the explicit target: use `USE keyspace` (without @primary/@replica suffix) before the INSERT and let VTGate route the write
  2. Send the INSERT to a tablet directly (vttablet) instead of vtgate if you need tablet-level targeting
  3. Split the workload: use targeted sessions for reads and a normal session for DML

Example fix

// before
USE commerce@replica;
INSERT INTO products (id, name) VALUES (1, 'x');
// after
USE commerce;
INSERT INTO products (id, name) VALUES (1, 'x');
Defensive patterns

Strategy: validation

Validate before calling

// Reject targeted destinations before sending DML
if (/^[^\s]+@(primary|replica|rdonly)$/.test(currentUseTarget)) {
  throw new Error('DML not allowed on targeted destination: ' + currentUseTarget);
}

Type guard

function isTargetedDestination(target) {
  return typeof target === 'string' && /@[a-z]+$/i.test(target);
}

Try / catch

try {
  await conn.query('INSERT INTO t VALUES (?)', [v]);
} catch (e) {
  if (String(e).includes('VT09017')) {
    await conn.query('USE commerce'); // drop the @tablet-type suffix and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Executing an INSERT while the session has a targeted destination (routing is *TargetedRouting) — e.g. after `USE ks@primary` or sending the query with an explicit tablet-type/tablet-target destination to vtgate.

Common situations: Scripts that set a tablet target for reads and reuse the same session for writes; tools pinning traffic to a specific replica or tablet type; testing setups that redirect all statements to a chosen destination.

Related errors


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