{"record":{"id":"128bf255b06eb1ab","repo":"bytebase/bytebase","slug":"type-q-expected-selectstmt-got-t","errorCode":null,"errorMessage":"type %q: expected SelectStmt, got %T","messagePattern":"type %q: expected SelectStmt, got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/parser/pg/query_span_loader_type_name.go","lineNumber":37,"sourceCode":"// typeNameFromString parses a PG type string into an *ast.TypeName by running\n// it through omni's SELECT parser via the \"SELECT NULL::<type>\" trick. This\n// reuses the stable SELECT parse path and avoids the DDL parse path that\n// breaks on BYT-9215 / BYT-9261 class inputs.\n//\n// Callers must only pass values originating from bytebase sync metadata. The\n// guardrails (single-statement, single-target) reject most malformed inputs\n// but are not a sanitization layer for arbitrary user input.\nfunc typeNameFromString(typeStr string) (*ast.TypeName, error) {\n\tstmts, err := ParsePg(\"SELECT NULL::\" + typeStr)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"parse type %q\", typeStr)\n\t}\n\tif len(stmts) != 1 {\n\t\treturn nil, errors.Errorf(\"type %q: expected 1 statement, got %d\", typeStr, len(stmts))\n\t}\n\tsel, ok := stmts[0].AST.(*ast.SelectStmt)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"type %q: expected SelectStmt, got %T\", typeStr, stmts[0].AST)\n\t}\n\tif sel.TargetList == nil || len(sel.TargetList.Items) != 1 {\n\t\treturn nil, errors.Errorf(\"type %q: expected 1 target\", typeStr)\n\t}\n\trt, ok := sel.TargetList.Items[0].(*ast.ResTarget)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"type %q: expected ResTarget, got %T\", typeStr, sel.TargetList.Items[0])\n\t}\n\tcast, ok := rt.Val.(*ast.TypeCast)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"type %q: expected TypeCast, got %T\", typeStr, rt.Val)\n\t}\n\treturn cast.TypeName, nil\n}\n\n// extractUserTypeRefs returns the user-defined type references embedded in a\n// PG type string. Built-in types, PG internal array forms (_name), and\n// system-schema-qualified types return nil.","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/parser/pg/query_span_loader_type_name.go#L19-L55","documentation":"After parsing \"SELECT NULL::<type>\", typeNameFromString asserts the single resulting statement is an *ast.SelectStmt. This error fires when the parser returns a different statement kind (e.g. an error-resilient node or a different AST wrapper), breaking the assumption the cast-extraction trick relies on. It indicates the parser produced an unexpected AST shape for the constructed query.","triggerScenarios":"ParsePg returns a slice whose first element's AST is not *ast.SelectStmt for the constructed \"SELECT NULL::<typeStr>\" string — e.g. if a type string manipulates the parse so the statement parses as something other than a plain SELECT, via buildCompositeTypeStmt/buildCreateStmt/buildCreateFunctionStmtFromSignature/mustTypeName.","commonSituations":"A PG engine or parser library version change that alters statement wrapping (e.g. returning a generic statement node), or exotic type strings that the parser normalizes differently than expected.","solutions":["Log the actual AST type (%T value in the message) to see what the parser returned","Check the parser library version for recent changes to how single SELECTs are wrapped and pin a known-good version","Add a case for the unexpected node type if it legitimately represents a type cast"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"typeName, err := typeNameFromString(typeStr)\nif err != nil {\n    return fmt.Errorf(\"cannot resolve type %q: %w\", typeStr, err)\n}","preventionTips":["Pin the PG parser library version and review its changelog on upgrades","Run TestLoaderTypeNameFromString in CI to catch AST shape drift early","Wrap loader calls so callers get the offending type string in their own error context"],"tags":["postgresql","parsing","ast-shape","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}