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 thisView on GitHub (pinned to 01a25a7d17)
Solutions
- Remove the LIMIT/ORDER BY clause and run the statement without it (chunk manually by primary key ranges if batching is needed)
- 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
- Avoid LIMIT and ORDER BY in vexec DELETE/UPDATE statements
- Batch deletions with WHERE key-range predicates instead of LIMIT
- Check the QueryPlanner docs for supported constructs before writing maintenance SQL
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
- cannot update immutable column
- attempted to execute unprepared query
- no primary found for shard
- table not supported by vexec
- value out of range
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/a040d4a1b255604d.
Report an issue: GitHub.