{"record":{"id":"71d4275caf8b2e5b","repo":"vitessio/vitess","slug":"errunpreparedquery","errorCode":"ErrUnpreparedQuery","errorMessage":"%w: call PlanQuery on a query planner first","messagePattern":"%w: call PlanQuery on a query planner first","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtctl/workflow/vexec/query_plan.go","lineNumber":57,"sourceCode":"\tExecute(ctx context.Context, target *topo.TabletInfo) (*querypb.QueryResult, error)\n\t// ExecuteScatter executes the planned query on the specified targets concurrently,\n\t// returning a mapping of the target tablet to a querypb.QueryResult.\n\tExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error)\n}\n\n// FixedQueryPlan wraps a planned query produced by a QueryPlanner. It executes\n// the same query with the same bind vals, regardless of the target.\ntype FixedQueryPlan struct {\n\tParsedQuery *sqlparser.ParsedQuery\n\n\tworkflow string\n\ttmc      tmclient.TabletManagerClient\n}\n\n// Execute is part of the QueryPlan interface.\nfunc (qp *FixedQueryPlan) Execute(ctx context.Context, target *topo.TabletInfo) (qr *querypb.QueryResult, err error) {\n\tif qp.ParsedQuery == nil {\n\t\treturn nil, fmt.Errorf(\"%w: call PlanQuery on a query planner first\", ErrUnpreparedQuery)\n\t}\n\n\ttargetAliasStr := target.AliasString()\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tlog.Warn(fmt.Sprintf(\"Result on %v: %v\", targetAliasStr, err))\n\t\t\treturn\n\t\t}\n\t}()\n\n\tqr, err = qp.tmc.VReplicationExec(ctx, target.Tablet, qp.ParsedQuery.Query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn qr, nil\n}\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtctl/workflow/vexec/query_plan.go#L39-L75","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nqp.Execute(ctx, target) // ParsedQuery nil\n// after\nif err := qp.PlanQuery(ctx); err != nil { return err }\nqr, err := qp.Execute(ctx, target)","handlingStrategy":"try-catch","validationCode":"if qp.ParsedQuery == nil {\n\treturn fmt.Errorf(\"cannot execute: query plan not prepared; call PlanQuery first\")\n}","typeGuard":"func isPrepared(qp *vexec.FixedQueryPlan) bool { return qp != nil && qp.ParsedQuery != nil }","tryCatchPattern":"qr, err := qp.Execute(ctx, target)\nif err != nil {\n\tif errors.Is(err, vexec.ErrUnpreparedQuery) {\n\t\tif perr := qp.PlanQuery(ctx); perr != nil { return perr }\n\t\tqr, err = qp.Execute(ctx, target)\n\t}\n\tif err != nil { return err }\n}","preventionTips":["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"],"tags":["vexec","query-plan","api-misuse","sentinel-error"],"backgroundTag":"unprepared-query-plan","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}