{"record":{"id":"7309cce36b5913e6","repo":"vitessio/vitess","slug":"w-sql-query-is-required","errorCode":null,"errorMessage":"%w: SQL query is required","messagePattern":"%w: SQL query is required","errorType":"validation","errorClass":"ErrInvalidRequest","httpStatus":null,"severity":"error","filePath":"go/vt/vtadmin/api.go","lineNumber":2619,"sourceCode":"\n\treturn res, nil\n}\n\n// VExplain is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) VExplain(ctx context.Context, req *vtadminpb.VExplainRequest) (*vtadminpb.VExplainResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.VExplain\")\n\tdefer span.Finish()\n\n\tif req.ClusterId == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: clusterID is required\", errors.ErrInvalidRequest)\n\t}\n\n\tif req.Keyspace == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: keyspace name is required\", errors.ErrInvalidRequest)\n\t}\n\n\tif req.Sql == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: SQL query is required\", errors.ErrInvalidRequest)\n\t}\n\n\tc, err := api.getClusterForRequest(req.ClusterId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !api.authz.IsAuthorized(ctx, c.ID, rbac.VExplainResource, rbac.GetAction) {\n\t\treturn nil, nil\n\t}\n\n\t// Parser with default options. New() itself initializes with default MySQL version.\n\tparser, err := sqlparser.New(sqlparser.Options{\n\t\tTruncateUILen:  512,\n\t\tTruncateErrLen: 0,\n\t})\n\tif err != nil {\n\t\treturn nil, err","sourceCodeStart":2601,"sourceCodeEnd":2637,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtadmin/api.go#L2601-L2637","documentation":"Returned by vtadmin API methods (wrapping a sentinel error) when a request omits the required SQL query text.","triggerScenarios":"Calling API.VExplain with ClusterId and Keyspace set but Sql equal to \"\".","commonSituations":"Text box or query builder submitted empty; query string lost in serialization (empty after trimming); template rendered without binding the SQL variable.","solutions":["Set req.Sql to the non-empty query to explain","Validate/trim the SQL client-side before invoking the RPC","Check the form/SDK path that drops the sql field"],"exampleFix":"// before\n{clusterId: \"prod-main\", keyspace: \"commerce\", sql: \"\"}\n// after\n{clusterId: \"prod-main\", keyspace: \"commerce\", sql: \"select * from users where id = 1\"}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(req.Sql) == \"\" {\n    return errors.New(\"sql is required for vexplain\")\n}","typeGuard":"func hasSQL(req *vtadminpb.VExplainRequest) bool {\n    return req != nil && strings.TrimSpace(req.Sql) != \"\"\n}","tryCatchPattern":"if !hasSQL(req) {\n    return errors.New(\"sql is required for vexplain\")\n}\nresp, err := client.VExplain(ctx, req)","preventionTips":["Trim and check user-supplied SQL before sending","Disable submit actions in UIs until the query field is non-empty","Log the outgoing request (sans secrets) to catch fields lost in serialization"],"tags":["vtadmin","validation","request","sql"],"backgroundTag":"missing-required-request-field","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}