bytebase/bytebase · error

pseudo constant select: expected SelectStmt, got %T

Error message

pseudo constant select: expected SelectStmt, got %T

What it means

wtPseudoConstantSelect parses a SQL string meant to be a 'pseudo constant' (a plain SELECT) and expects the top-level statement to be a SelectStmt. After unwrapping the RawStmt wrapper, the first statement's concrete AST node type did not match *ast.SelectStmt. This is an internal contract violation: the caller passed SQL whose statement kind is not a simple SELECT, so the loader cannot safely continue walking the AST.

Source

Thrown at backend/plugin/schema/pg/walk_through_loader.go:1068

	}
	if len(targets) == 0 {
		targets = []string{"NULL::text"}
	}
	sql := "SELECT " + strings.Join(targets, ", ")
	nodes, err := omniparser.Parse(sql)
	if err != nil {
		return nil, errors.Wrap(err, "parse pseudo constant select")
	}
	if nodes == nil || len(nodes.Items) != 1 {
		return nil, errors.New("pseudo constant select: expected 1 statement")
	}
	node := nodes.Items[0]
	if raw, ok := node.(*ast.RawStmt); ok {
		node = raw.Stmt
	}
	sel, ok := node.(*ast.SelectStmt)
	if !ok {
		return nil, errors.Errorf("pseudo constant select: expected SelectStmt, got %T", node)
	}
	return sel, nil
}

// ---- column name helpers ----

func wtColNames(tbl *storepb.TableMetadata) []string {
	if tbl == nil {
		return nil
	}
	seen := make(map[string]bool, len(tbl.Columns))
	out := make([]string, 0, len(tbl.Columns))
	for _, col := range tbl.Columns {
		if col.Name == "" || seen[col.Name] {
			continue
		}
		seen[col.Name] = true
		out = append(out, col.Name)

View on GitHub (pinned to 1870550677)

Solutions

  1. Inspect the SQL string passed in and ensure the first statement is exactly one plain SELECT statement.
  2. Strip wrapper statements (semicolons, EXPLAIN, extra statements) so only the SELECT body remains.
  3. Log the offending %T value from the error to see which AST node actually arrived and adjust the caller accordingly.
  4. If non-SELECT pseudo constants must be supported, extend wtPseudoConstantSelect to handle that node type instead of erroring.

Example fix

// before
return wtPseudoConstantSelect(ctx, "CREATE VIEW v AS SELECT 1")
// after
return wtPseudoConstantSelect(ctx, "SELECT 1")
Defensive patterns

Strategy: validation

Validate before calling

func isSingleSelect(sql string) bool {
	trimmed := strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(sql), ";"))
	return strings.HasPrefix(strings.ToLower(trimmed), "select ") && !strings.Contains(trimmed, ";")
}
// call wtPseudoConstantSelect only if isSingleSelect(sql)

Type guard

sel, ok := node.(*ast.SelectStmt)
if !ok { return fmt.Errorf("unexpected node %T", node) }

Try / catch

sel, err := wtPseudoConstantSelect(ctx, sql)
if err != nil {
	return fmt.Errorf("pseudo constant %q: %w", sql, err)
}

Prevention

When it happens

Trigger: Calling wtPseudoConstantSelect (directly or via wtPseudoViewStmt/wtPseudoMatViewStmt) with SQL that parses to a non-SELECT statement, e.g. an INSERT, a utility statement (SET, VACUUM), a multi-statement string where the first item is not a SELECT, or empty/placeholder text that the parser still returns as some other node type.

Common situations: A walkthrough definition file contains a view body or materialized-view definition stored in a non-SELECT form; a typo or edited fixture replaced the SELECT with another statement; a multi-statement SQL blob was passed where only the SELECT body was expected.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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