vitessio/vitess · error
ErrUnpreparedQuery
ErrUnpreparedQuery
Error message
%w: call PlanQuery on a query planner first
What it means
FixedQueryPlan.Execute runs a pre-planned query on a tablet. If PlanQuery was never called, ParsedQuery is nil and Execute returns the sentinel ErrUnpreparedQuery wrapped via %w (so errors.Is works). It is an API-misuse guard: the query planner must produce a ParsedQuery before results can be executed against a target tablet.
Source
Thrown at go/vt/vtctl/workflow/vexec/query_plan.go:57
Execute(ctx context.Context, target *topo.TabletInfo) (*querypb.QueryResult, error)
// ExecuteScatter executes the planned query on the specified targets concurrently,
// returning a mapping of the target tablet to a querypb.QueryResult.
ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error)
}
// FixedQueryPlan wraps a planned query produced by a QueryPlanner. It executes
// the same query with the same bind vals, regardless of the target.
type FixedQueryPlan struct {
ParsedQuery *sqlparser.ParsedQuery
workflow string
tmc tmclient.TabletManagerClient
}
// Execute is part of the QueryPlan interface.
func (qp *FixedQueryPlan) Execute(ctx context.Context, target *topo.TabletInfo) (qr *querypb.QueryResult, err error) {
if qp.ParsedQuery == nil {
return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery)
}
targetAliasStr := target.AliasString()
defer func() {
if err != nil {
log.Warn(fmt.Sprintf("Result on %v: %v", targetAliasStr, err))
return
}
}()
qr, err = qp.tmc.VReplicationExec(ctx, target.Tablet, qp.ParsedQuery.Query)
if err != nil {
return nil, err
}
return qr, nil
}
View on GitHub (pinned to 01a25a7d17)
Solutions
- Always call PlanQuery (via the vexec query planner) before Execute on the same plan.
- Check the error from PlanQuery and abort before calling Execute.
- If constructing vexec plans programmatically, set ParsedQuery explicitly or use the planner API rather than the struct literal.
- In code, match errors.Is(err, vexec.ErrUnpreparedQuery) to detect the missing-plan condition cleanly.
Example fix
// before
qp.Execute(ctx, target) // ParsedQuery nil
// after
if err := qp.PlanQuery(ctx); err != nil { return err }
qr, err := qp.Execute(ctx, target) Defensive patterns
Strategy: try-catch
Validate before calling
if qp.ParsedQuery == nil {
return fmt.Errorf("cannot execute: query plan not prepared; call PlanQuery first")
} Type guard
func isPrepared(qp *vexec.FixedQueryPlan) bool { return qp != nil && qp.ParsedQuery != nil } Try / catch
qr, err := qp.Execute(ctx, target)
if err != nil {
if errors.Is(err, vexec.ErrUnpreparedQuery) {
if perr := qp.PlanQuery(ctx); perr != nil { return perr }
qr, err = qp.Execute(ctx, target)
}
if err != nil { return err }
} Prevention
- Always drive FixedQueryPlan through its planner instead of building the struct directly
- Check the PlanQuery return error and short-circuit before Execute
- Use errors.Is(err, vexec.ErrUnpreparedQuery) in wrappers to detect ordering bugs early
- Add an assertion/log when Execute is called with a nil ParsedQuery during development
When it happens
Trigger: Calling Execute on a FixedQueryPlan returned by a planner whose PlanQuery method was never invoked, or after PlanQuery failed silently / stored nothing into ParsedQuery (e.g. nil query result path in vexec workflows).
Common situations: Custom tooling constructs a FixedQueryPlan directly instead of going through the planner; error handling skips a failed PlanQuery and proceeds to Execute; refactor changes call order so Execute runs before PlanQuery.
Related errors
- ReadFile cannot be called on read-write backup
- AddFile cannot be called on read-only backup
- EndBackup cannot be called on read-only backup
- AbortBackup cannot be called on read-only backup
- ReadFile cannot be called on read-write backup
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/71d4275caf8b2e5b.
Report an issue: GitHub.