bytebase/bytebase · error

default %q: expected SelectStmt

Error message

default %q: expected SelectStmt

What it means

After unwrapping RawStmt, wtParseDefaultExpr asserts the single statement is *ast.SelectStmt, since it built 'SELECT <expr>'. If the parser classified the input as another statement kind, this error fires. Unlike sibling checks it omits the %T detail, so the actual node type must be inspected at the call site.

Source

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

	return cast.TypeName, nil
}

// wtParseDefaultExpr parses a default expression string and returns the AST node.
func wtParseDefaultExpr(expr string) (ast.Node, error) {
	nodes, err := omniparser.Parse("SELECT " + expr)
	if err != nil {
		return nil, errors.Wrapf(err, "parse default %q", expr)
	}
	if nodes == nil || len(nodes.Items) != 1 {
		return nil, errors.Errorf("default %q: expected 1 statement", expr)
	}
	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("default %q: expected SelectStmt", expr)
	}
	if sel.TargetList == nil || len(sel.TargetList.Items) != 1 {
		return nil, errors.Errorf("default %q: expected 1 target", expr)
	}
	rt, ok := sel.TargetList.Items[0].(*ast.ResTarget)
	if !ok {
		return nil, errors.Errorf("default %q: expected ResTarget", expr)
	}
	return rt.Val, nil
}

// wtParseSelectBody parses a SQL string into *ast.SelectStmt.
func wtParseSelectBody(sql string) (*ast.SelectStmt, error) {
	nodes, err := omniparser.Parse(sql)
	if err != nil {
		return nil, errors.Wrap(err, "parse")
	}
	if nodes == nil || len(nodes.Items) != 1 {

View on GitHub (pinned to 1870550677)

Solutions

  1. Ensure the default value is a bare expression, not a full SQL statement.
  2. Remove any leading keywords/comments from the expression string.
  3. Temporarily log the parsed node type (or add %T to the message) to identify the unexpected shape.
  4. Check omni parser version changes if previously valid defaults now fail.

Example fix

// before
wtParseDefaultExpr("SELECT now()")
// after
wtParseDefaultExpr("now()")
Defensive patterns

Strategy: validation

Validate before calling

stmtKeywords := []string{"select", "insert", "update", "delete", "with", "set"}
first := strings.Fields(strings.ToLower(expr))
if len(first) > 0 && slices.Contains(stmtKeywords, first[0]) {
	return errors.New("default must be a bare expression, not a statement")
}

Type guard

sel, ok := node.(*ast.SelectStmt)
if !ok { return fmt.Errorf("default %q parsed as %T", expr, node) }

Try / catch

node, err := wtParseDefaultExpr(expr)
if err != nil {
	return fmt.Errorf("default %q not an expression: %w", expr, err)
}

Prevention

When it happens

Trigger: A default expression whose text begins with a non-expression keyword (e.g. 'SELECT', 'INSERT', 'SET') so 'SELECT '+expr parses to a nested or different statement; parser-version-specific classification of the constructed SQL.

Common situations: Fixture defaults accidentally containing 'SELECT ...' or other statements; expressions copied with leading comments or keywords; omni parser upgrades changing how constructed queries are classified.

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/9593b2accebf7c9d. Report an issue: GitHub.