bytebase/bytebase · error

expected exactly 1 statement, got %d

Error message

expected exactly 1 statement, got %d

What it means

getOmniQuerySpan parses the SQL text via the omni parser and requires exactly one statement to compute a query span. If the parser returned zero or more than one statement (batch input with multiple statements separated by semicolons, or an unexpected parse result), it cannot determine a single root span and returns this error. Zero-statement and empty-AST cases are handled explicitly before this check.

Source

Thrown at backend/plugin/parser/tsql/query_span_extractor_omni.go:48

// getOmniQuerySpan is the public entry point. Mirrors the ANTLR getQuerySpan flow.
func (q *omniQuerySpanExtractor) getOmniQuerySpan(ctx context.Context, statement string) (*base.QuerySpan, error) {
	q.ctx = ctx
	q.source = statement

	stmts, err := ParseTSQLOmni(statement)
	if err != nil {
		return nil, err
	}
	if len(stmts) == 0 {
		return &base.QuerySpan{
			Type:          base.Select,
			SourceColumns: make(base.SourceColumnSet),
			Results:       []base.QuerySpanResult{},
		}, nil
	}
	if len(stmts) != 1 {
		return nil, errors.Errorf("expected exactly 1 statement, got %d", len(stmts))
	}

	root := stmts[0].AST
	if root == nil {
		// An empty statement. Match ANTLR's zero-AST behavior.
		return &base.QuerySpan{
			Type:          base.Select,
			SourceColumns: make(base.SourceColumnSet),
			Results:       []base.QuerySpanResult{},
		}, nil
	}
	if _, isGo := root.(*ast.GoStmt); isGo {
		// A bare "GO" batch separator. ANTLR discards it and returns an empty
		// Select span; match that.
		return &base.QuerySpan{
			Type:          base.Select,
			SourceColumns: make(base.SourceColumnSet),
			Results:       []base.QuerySpanResult{},

View on GitHub (pinned to 1870550677)

Solutions

  1. Split the input into individual statements and call the extractor once per statement, aggregating spans yourself.
  2. Strip non-query prologue statements (SET options, DECLARE) before span extraction, or run extraction only on the SELECT/DML statements.
  3. If a single statement is expected but the parser returned several, verify there are no stray semicolons or GO batch separators.

Example fix

// before: extract the whole batch
span, err := GetQuerySpan(batchSQL) // "expected exactly 1 statement, got 3"
// after: split and extract per statement
for _, stmt := range splitStatements(batchSQL) {
    span, err := GetQuerySpan(stmt)
}
Defensive patterns

Strategy: validation

Validate before calling

stmts := splitStatements(sql)
if len(stmts) != 1 {
    return fmt.Errorf("span extraction needs a single statement, got %d", len(stmts))
}

Try / catch

span, err := getOmniQuerySpan(content)
if err != nil && strings.Contains(err.Error(), "expected exactly 1 statement") {
    for _, stmt := range splitStatements(content) {
        span, err = getOmniQuerySpan(stmt)
    }
}

Prevention

When it happens

Trigger: Passing a TSQL batch containing multiple semicolon-separated statements to the query span extractor, or any input the omni parser splits into more than one AST root.

Common situations: Users submitting multi-statement scripts (e.g. `SET NOCOUNT ON; SELECT ...;`) for data-care/lineage analysis, or migration scripts pasted in whole instead of per-statement analysis.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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