vitessio/vitess · error · ErrUnsupportedQueryConstruct

unsupported query construct

Error message

unsupported query construct

What it means

ErrUnsupportedQueryConstruct is returned when a query is of a kind the planner could handle in principle, but includes a construct VReplication vexec does not implement — e.g. a DELETE with a LIMIT clause. It is deliberately distinct from ErrUnsupportedQuery (whole query kind unsupported, like CREATE TABLE).

Source

Thrown at go/vt/vtctl/workflow/vexec/query_planner.go:40

	"strconv"

	"vitess.io/vitess/go/vt/sqlparser"
	"vitess.io/vitess/go/vt/vttablet/tmclient"
)

var ( // Query planning errors.
	// ErrCannotUpdateImmutableColumn is returned when attempting to plan a
	// query that updates a column that should be treated as immutable.
	ErrCannotUpdateImmutableColumn = errors.New("cannot update immutable column")
	// ErrUnsupportedQueryConstruct is returned when a particular query
	// construct is unsupported by a QueryPlanner, despite the more general kind
	// of query being supported.
	//
	// For example, VReplication supports DELETEs, but does not support DELETEs
	// with LIMIT clauses, so planning a "DELETE ... LIMIT" will return
	// ErrUnsupportedQueryConstruct rather than a "CREATE TABLE", which would
	// return an ErrUnsupportedQuery.
	ErrUnsupportedQueryConstruct = errors.New("unsupported query construct")
)

// Query execution errors.
// ErrUnpreparedQuery is returned when attempting to execute an unprepared
// QueryPlan.
var ErrUnpreparedQuery = errors.New("attempted to execute unprepared query")

// QueryPlanner defines the interface that VExec uses to build QueryPlans for
// various vexec workflows. A given vexec table, which is to say a table in the
// "_vt" database, will have at most one QueryPlanner implementation, which is
// responsible for defining both what queries are supported for that table, as
// well as how to build plans for those queries.
//
// VReplicationQueryPlanner is a good example implementation to refer to.
type QueryPlanner interface {
	// (NOTE:@ajm188) I don't think this method fits on the query planner. To
	// me, especially given that it's only implemented by the vrep query planner
	// in the old implementation (the schema migration query planner no-ops this

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the LIMIT/ORDER BY clause and run the statement without it (chunk manually by primary key ranges if batching is needed)
  2. Delete rows in batches using WHERE id < X predicates instead of LIMIT

Example fix

// before
DELETE FROM _vt.vreplication WHERE workflow = 'sales' LIMIT 100
// ErrUnsupportedQueryConstruct
// after
DELETE FROM _vt.vreplication WHERE workflow = 'sales' AND id <= 1000
Defensive patterns

Strategy: type-guard

Validate before calling

switch stmt := parsed.(type) {
case *sqlparser.Delete:
    if stmt.Limit != nil {
        return ErrUnsupportedQueryConstruct
    }
}

Type guard

func hasLimit(n sqlparser.SQLNode) bool {
    d, ok := n.(*sqlparser.Delete)
    return ok && d.Limit != nil
}

Try / catch

plan, err := planner.PlanDelete(stmt)
if errors.Is(err, workflow.ErrUnsupportedQueryConstruct) {
    return fmt.Errorf("re-run query without LIMIT/ORDER BY: %w", err)
}

Prevention

When it happens

Trigger: planDelete or planUpdate encounters a syntax node such as a LIMIT/OORDER on a DELETE/UPDATE being planned for a vexec workflow table.

Common situations: Operators adding LIMIT to cleanup DELETEs on _vt.vreplication to batch deletion, or generic SQL-migration tooling emitting ORDER BY/LIMIT in maintenance UPDATEs.

Related errors


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