bytebase/bytebase · error
type %q: expected SelectStmt, got %T
Error message
type %q: expected SelectStmt, got %T
What it means
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.
Source
Thrown at backend/plugin/parser/pg/query_span_loader_type_name.go:37
// typeNameFromString parses a PG type string into an *ast.TypeName by running
// it through omni's SELECT parser via the "SELECT NULL::<type>" trick. This
// reuses the stable SELECT parse path and avoids the DDL parse path that
// breaks on BYT-9215 / BYT-9261 class inputs.
//
// Callers must only pass values originating from bytebase sync metadata. The
// guardrails (single-statement, single-target) reject most malformed inputs
// but are not a sanitization layer for arbitrary user input.
func typeNameFromString(typeStr string) (*ast.TypeName, error) {
stmts, err := ParsePg("SELECT NULL::" + typeStr)
if err != nil {
return nil, errors.Wrapf(err, "parse type %q", typeStr)
}
if len(stmts) != 1 {
return nil, errors.Errorf("type %q: expected 1 statement, got %d", typeStr, len(stmts))
}
sel, ok := stmts[0].AST.(*ast.SelectStmt)
if !ok {
return nil, errors.Errorf("type %q: expected SelectStmt, got %T", typeStr, stmts[0].AST)
}
if sel.TargetList == nil || len(sel.TargetList.Items) != 1 {
return nil, errors.Errorf("type %q: expected 1 target", typeStr)
}
rt, ok := sel.TargetList.Items[0].(*ast.ResTarget)
if !ok {
return nil, errors.Errorf("type %q: expected ResTarget, got %T", typeStr, sel.TargetList.Items[0])
}
cast, ok := rt.Val.(*ast.TypeCast)
if !ok {
return nil, errors.Errorf("type %q: expected TypeCast, got %T", typeStr, rt.Val)
}
return cast.TypeName, nil
}
// extractUserTypeRefs returns the user-defined type references embedded in a
// PG type string. Built-in types, PG internal array forms (_name), and
// system-schema-qualified types return nil.View on GitHub (pinned to 1870550677)
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
Defensive patterns
Strategy: try-catch
Try / catch
typeName, err := typeNameFromString(typeStr)
if err != nil {
return fmt.Errorf("cannot resolve type %q: %w", typeStr, err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- type %q: expected ResTarget, got %T
- type %q: expected 1 statement, got %d
- type %q: expected 1 target
- type %q: expected TypeCast, got %T
- database name not found
AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06).
Data as JSON: /api/errors/128bf255b06eb1ab.
Report an issue: GitHub.