bytebase/bytebase · error

could not find statement at position (line %d:%d - %d:%d)

Error message

could not find statement at position (line %d:%d - %d:%d)

What it means

After parsing, GenerateRestoreSQL looks for a restorable DML AST node whose computed statement position range falls inside the backup item's StartPosition–EndPosition. If no such node is found (targetResult stays nil), it throws "could not find statement at position" with the offending line:column range. This means the positions recorded in the backup item do not map to any parsed DML statement.

Source

Thrown at backend/plugin/parser/tsql/restore.go:55

		return "", err
	}

	// Find the AST that contains the statement at the backup position
	var targetResult ast.Node
	for _, parsedStatement := range parsedStatements {
		node, ok := GetOmniNode(parsedStatement.AST)
		if !ok || node == nil || !isRestorableDML(node) {
			continue
		}
		start, end := statementPositions(parsedStatement.Start, parsedStatement.Text, dmlNodeLoc(node))
		if inRange(start, end, backupItem.StartPosition, backupItem.EndPosition) {
			targetResult = node
			break
		}
	}

	if targetResult == nil {
		return "", errors.Errorf("could not find statement at position (line %d:%d - %d:%d)",
			backupItem.StartPosition.Line, backupItem.StartPosition.Column,
			backupItem.EndPosition.Line, backupItem.EndPosition.Column)
	}

	sqlForComment, truncated := common.TruncateString(originalSQL, maxCommentLength)
	if truncated {
		sqlForComment += "..."
	}
	return doGenerate(ctx, rCtx, sqlForComment, targetResult, backupItem)
}

func doGenerate(ctx context.Context, rCtx base.RestoreContext, sqlForComment string, node ast.Node, backupItem *storepb.PriorBackupDetail_Item) (string, error) {
	_, _, sourceDatabase, err := common.GetDatabaseResourceName(backupItem.SourceTable.Database)
	if err != nil {
		return "", errors.Wrapf(err, "failed to get source database ID for %s", backupItem.SourceTable.Database)
	}
	_, _, targetDatabase, err := common.GetDatabaseResourceName(backupItem.TargetTable.Database)
	if err != nil {

View on GitHub (pinned to 1870550677)

Solutions

  1. Re-derive StartPosition/EndPosition from the same statement text passed to GenerateRestoreSQL — regenerate the backup item instead of reusing stale positions.
  2. Normalize line endings and recompute positions if the text was edited or migrated between systems.
  3. Confirm the statement at the position is an UPDATE or DELETE; convert the restore flow to handle only supported DML.
  4. Add logging of computed statementPositions ranges to compare against backupItem positions when debugging mismatches.

Example fix

// before
// stale positions from an older text version
backupItem.StartPosition = &storepb.Position{Line: 3, Column: 5}
// after
// recompute positions from current text
start, end := statementPositions(parsed.Start, parsed.Text, dmlNodeLoc(node))
backupItem.StartPosition, backupItem.EndPosition = start, end
Defensive patterns

Strategy: validation

Validate before calling

// recompute positions from the same text passed to GenerateRestoreSQL and compare
start, end := statementPositions(parsed.Start, parsed.Text, dmlNodeLoc(node))
if !inRange(start, end, backupItem.StartPosition, backupItem.EndPosition) {
    return errors.New("stale backup item positions; regenerate against current text")
}

Try / catch

sql, err := tsql.GenerateRestoreSQL(ctx, rCtx, statement, backupItem)
if err != nil && strings.HasPrefix(err.Error(), "could not find statement at position") {
    // positions are stale: rebuild the backup item from the current text
}

Prevention

When it happens

Trigger: The backup item's Start/EndPosition were computed against a different (older or reformatted) version of the SQL text; the statement at those positions is not an UPDATE/DELETE (filtered out by isRestorableDML); GetOmniNode fails to expose the DML node so the loop skips it.

Common situations: Statement text re-saved with changed indentation or comments shifting byte offsets; mixed line endings (CRLF vs LF) altering column math; a version upgrade changing parser offsets; restoring a statement type (e.g. INSERT) that is never matched.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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