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

  1. Rewrite the statement as a single-table DELETE with a WHERE clause (optionally using subqueries)
  2. Remove the USING / multi-table target syntax before passing to PlanQuery
  3. 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

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


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