vitessio/vitess · error
ErrUnsupportedQueryConstruct
ErrUnsupportedQueryConstruct
Error message
%w: DELETE must not have USING clause (have: %v): %v
What it means
The VReplication query planner supports only simple DELETE statements; a DELETE with a USING clause (multi-table delete targets) is rejected as ErrUnsupportedQueryConstruct. The error includes the offending targets and the full SQL text.
Source
Thrown at go/vt/vtctl/workflow/vexec/query_planner.go:157
// QueryParams is part of the QueryPlanner interface. A VReplicationQueryPlanner
// will attach the following WHERE clauses iff (a) DBName, Workflow are set,
// respectively, and (b) db_name and workflow do not appear in the original
// query's WHERE clause:
//
// WHERE (db_name = {{ .DBName }} AND)? (workflow = {{ .Workflow }} AND)? {{ .OriginalWhere }}
func (planner *VReplicationQueryPlanner) QueryParams() QueryParams {
return QueryParams{
DBName: planner.dbname,
DBNameColumn: "db_name",
Workflow: planner.workflow,
WorkflowColumn: "workflow",
}
}
func (planner *VReplicationQueryPlanner) planDelete(del *sqlparser.Delete) (*FixedQueryPlan, error) {
if del.Targets != nil {
return nil, fmt.Errorf(
"%w: DELETE must not have USING clause (have: %v): %v",
ErrUnsupportedQueryConstruct,
del.Targets,
sqlparser.String(del),
)
}
if del.Partitions != nil {
return nil, fmt.Errorf(
"%w: DELETE must not have explicit partitions (have: %v): %v",
ErrUnsupportedQueryConstruct,
del.Partitions,
sqlparser.String(del),
)
}
if del.OrderBy != nil || del.Limit != nil {
return nil, fmt.Errorf(View on GitHub (pinned to 01a25a7d17)
Solutions
- Rewrite the statement as a single-table DELETE with a WHERE clause (optionally using subqueries)
- Remove the USING / multi-table target syntax before passing to PlanQuery
- Run such deletes outside the VReplication planner (e.g. directly on the shard via vtgate) if multi-table semantics are required
Example fix
// before DELETE FROM _vt.vreplication USING _vt.copy_state WHERE copy_state.vrepl_id = _vt.vreplication.id // after DELETE FROM _vt.copy_state WHERE vrepl_id IN (SELECT id FROM _vt.vreplication WHERE ...)
Defensive patterns
Strategy: validation
Validate before calling
if del, ok := stmt.(*sqlparser.Delete); ok && del.Targets != nil {
return fmt.Errorf("DELETE with USING clause is not supported")
} Type guard
func isSimpleDelete(stmt sqlparser.Statement) bool {
del, ok := stmt.(*sqlparser.Delete)
return ok && del.Targets == nil
} Try / catch
plan, err := planner.PlanQuery(ctx, stmt)
if errors.Is(err, vexec.ErrUnsupportedQueryConstruct) {
return fmt.Errorf("rewrite DELETE as single-table delete: %w", err)
} Prevention
- Write single-table DELETEs with WHERE clauses only
- Replace multi-table delete patterns with subquery-based deletes
- Lint workflow SQL for USING/multi-table delete syntax before planning
When it happens
Trigger: Calling PlanQuery with a parsed *sqlparser.Delete whose Targets field is non-nil, i.e. SQL of the form DELETE t1 FROM t1 USING ... / DELETE t1, t2 FROM ...
Common situations: Copying multi-table DELETE statements written for direct MySQL use into a VReplication workflow context; generated SQL from ORMs that emit USING-style deletes.
Related errors
- ErrUnsupportedQuery
- ErrCannotUpdateImmutableColumn
- unsupported construct: %v
- --batch-size only allowed when all queries are CREATE TABLE|
- UnescapeID err: invalid input identifier '%s'
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/db3157097578222a.
Report an issue: GitHub.