{"record":{"id":"9ae8a7741e04cb5e","repo":"vitessio/vitess","slug":"attempted-to-execute-unprepared-query","errorCode":null,"errorMessage":"attempted to execute unprepared query","messagePattern":"attempted to execute unprepared query","errorType":"error_code","errorClass":"ErrUnpreparedQuery","httpStatus":null,"severity":"error","filePath":"go/vt/vtctl/workflow/vexec/query_planner.go","lineNumber":46,"sourceCode":"var ( // Query planning errors.\n\t// ErrCannotUpdateImmutableColumn is returned when attempting to plan a\n\t// query that updates a column that should be treated as immutable.\n\tErrCannotUpdateImmutableColumn = errors.New(\"cannot update immutable column\")\n\t// ErrUnsupportedQueryConstruct is returned when a particular query\n\t// construct is unsupported by a QueryPlanner, despite the more general kind\n\t// of query being supported.\n\t//\n\t// For example, VReplication supports DELETEs, but does not support DELETEs\n\t// with LIMIT clauses, so planning a \"DELETE ... LIMIT\" will return\n\t// ErrUnsupportedQueryConstruct rather than a \"CREATE TABLE\", which would\n\t// return an ErrUnsupportedQuery.\n\tErrUnsupportedQueryConstruct = errors.New(\"unsupported query construct\")\n)\n\n// Query execution errors.\n// ErrUnpreparedQuery is returned when attempting to execute an unprepared\n// QueryPlan.\nvar ErrUnpreparedQuery = errors.New(\"attempted to execute unprepared query\")\n\n// QueryPlanner defines the interface that VExec uses to build QueryPlans for\n// various vexec workflows. A given vexec table, which is to say a table in the\n// \"_vt\" database, will have at most one QueryPlanner implementation, which is\n// responsible for defining both what queries are supported for that table, as\n// well as how to build plans for those queries.\n//\n// VReplicationQueryPlanner is a good example implementation to refer to.\ntype QueryPlanner interface {\n\t// (NOTE:@ajm188) I don't think this method fits on the query planner. To\n\t// me, especially given that it's only implemented by the vrep query planner\n\t// in the old implementation (the schema migration query planner no-ops this\n\t// method), this fits better on our workflow.Manager struct, probably as a\n\t// method called something like \"VReplicationExec(ctx, query, Options{DryRun: true})\"\n\t// DryRun(ctx context.Context) error\n\n\t// PlanQuery constructs and returns a QueryPlan for a given statement. The\n\t// resulting QueryPlan is suitable for repeated, concurrent use.","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtctl/workflow/vexec/query_planner.go#L28-L64","documentation":"ErrUnpreparedQuery is returned when a QueryPlan is executed without first being prepared/bound. The VExec execution path (Execute, ExecuteScatter) expects a plan that went through preparation; executing a nil or unbound plan is a programming error and surfaces as this sentinel.","triggerScenarios":"Calling QueryPlan.Execute or ExecuteScatter before the plan was prepared (e.g. planner returned an error that was ignored, or a plan struct with a nil bound query is passed in).","commonSituations":"Bugs in new QueryPlanner implementations, ignoring the error from planDelete/planUpdate and proceeding to execute, or race conditions clearing prepared plans before execution.","solutions":["Check and handle the error from the planning step before calling Execute/ExecuteScatter","Ensure the QueryPlan was fully prepared (non-nil bound query/fields) before execution","Fix the planner implementation so it either returns a valid plan or an explicit unsupported-query error"],"exampleFix":"// before\nplan, _ := planner.PlanDelete(stmt)\nqp.Execute(ctx, plan) // ErrUnpreparedQuery\n// after\nplan, err := planner.PlanDelete(stmt)\nif err != nil {\n    return err\n}\nreturn qp.Execute(ctx, plan)","handlingStrategy":"validation","validationCode":"if plan == nil || plan.BoundQuery == nil {\n    return ErrUnpreparedQuery\n}","typeGuard":"func isPrepared(p *QueryPlan) bool {\n    return p != nil && p.BoundQuery != nil && p.BoundQuery.Query != \"\"\n}","tryCatchPattern":"plan, err := planner.PlanUpdate(stmt)\nif err != nil { return err }\nif !isPrepared(plan) { return ErrUnpreparedQuery }\nreturn qp.Execute(ctx, plan)","preventionTips":["Always propagate the planning error before executing","Assert plans are non-nil in planner unit tests","Bind queries at construction time so an unprepared plan is unrepresentable"],"tags":["vreplication","vexec","query-planner"],"backgroundTag":"unprepared-query","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}