vitessio/vitess · error · VitessError
VT12001
VT12001
Error message
VT12001: unsupported: REPLACE INTO using select statement
What it means
VT12001: REPLACE INTO that takes its rows from a SELECT statement is not supported when the table requires delete-before-insert handling. createOperatorFromInsert must build a plan that deletes existing rows before inserting, and that construction only works with literal VALUES rows; a select-statement source is rejected.
Source
Thrown at go/vt/vtgate/planbuilder/operators/insert.go:134
deleteBeforeInsert := false
if ins.Action == sqlparser.ReplaceAct &&
(ctx.SemTable.ForeignKeysPresent() || vTbl.Keyspace.Sharded) &&
(len(vTbl.PrimaryKey) > 0 || len(vTbl.UniqueKeys) > 0) {
// this needs a delete before insert as there can be row clash which needs to be deleted first.
ins.Action = sqlparser.InsertAct
deleteBeforeInsert = true
}
insOp := checkAndCreateInsertOperator(ctx, ins, vTbl, routing)
if !deleteBeforeInsert {
return insOp
}
rows, isRows := ins.Rows.(sqlparser.Values)
if !isRows {
panic(vterrors.VT12001("REPLACE INTO using select statement"))
}
pkCompExpr := pkCompExpression(vTbl, ins, rows)
uniqKeyCompExprs := uniqKeyCompExpressions(vTbl, ins, rows)
whereExpr := getWhereCondExpr(append(uniqKeyCompExprs, pkCompExpr))
delStmt := &sqlparser.Delete{
Comments: ins.Comments,
TableExprs: sqlparser.TableExprs{sqlparser.Clone(ins.Table)},
Where: sqlparser.NewWhere(sqlparser.WhereClause, whereExpr),
}
delOp := createOpFromStmt(ctx, delStmt, false, "")
return &Sequential{Sources: []Operator{delOp, insOp}}
}
func checkAndCreateInsertOperator(ctx *plancontext.PlanningContext, ins *sqlparser.Insert, vTbl *vindexes.BaseTable, routing Routing) Operator {
insOp := createInsertOperator(ctx, ins, vTbl, routing)
View on GitHub (pinned to 01a25a7d17)
Solutions
- Rewrite as two statements: INSERT INTO ... SELECT, preceded by explicit DELETEs for conflicting keys
- Load the SELECT results into the application and issue REPLACE INTO with literal VALUES
- Use INSERT ... ON DUPLICATE KEY UPDATE instead of REPLACE if rows should update rather than delete+insert
- If the table does not need delete-before-insert behavior, verify table/keyspace configuration (this path is reached only when deleteBeforeInsert is set)
Example fix
-- before REPLACE INTO t SELECT * FROM t2; -- after INSERT INTO t (a,b) SELECT a,b FROM t2 ON DUPLICATE KEY UPDATE b = VALUES(b);
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check: REPLACE INTO must use VALUES, not SELECT, on sharded tables
func replaceUsesSelect(stmt sqlparser.Statement) bool {
ins, ok := stmt.(*sqlparser.Insert)
if !ok || ins.Action != sqlparser.ReplaceAct { return false }
_, isValues := ins.Rows.(sqlparser.Values)
return !isValues
} Type guard
rows, isValues := ins.Rows.(sqlparser.Values) // isValues must be true for REPLACE INTO plans
Try / catch
_, err := db.ExecContext(ctx, q)
if err != nil && strings.Contains(err.Error(), "VT12001") && strings.Contains(err.Error(), "REPLACE INTO") {
// fall back to DELETE + INSERT ... SELECT strategy
} Prevention
- Use INSERT ... ON DUPLICATE KEY UPDATE instead of REPLACE INTO in sharded schemas
- Split REPLACE INTO ... SELECT into DELETE + INSERT ... SELECT statements
- Audit application SQL for REPLACE usage before sharding
- Keep REPLACE INTO only on unsharded single-shard tables
When it happens
Trigger: `REPLACE INTO tbl SELECT ...` (ins.Rows is not sqlparser.Values) on a sharded/managed table where deleteBeforeInsert is true — i.e. the table has primary key or unique key handling requiring the delete-before-insert plan.
Common situations: Migrating MySQL applications to Vitess that use REPLACE INTO ... SELECT for data copies; scripts that dedupe rows via REPLACE INTO with a SELECT source on sharded keyspaces.
Related errors
- VT12002
- malformed spec: MinKey/MaxKey cannot be in the middle of the
- malformed spec: shard limits should be in order: %q
- the shard count must be > 0: %v
- the index of the shard must be less than the total number of
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/51dc7f00c295a197.
Report an issue: GitHub.