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

  1. Log the actual AST type (%T value in the message) to see what the parser returned
  2. Check the parser library version for recent changes to how single SELECTs are wrapped and pin a known-good version
  3. 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

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


AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06). Data as JSON: /api/errors/128bf255b06eb1ab. Report an issue: GitHub.