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
- Re-derive StartPosition/EndPosition from the same statement text passed to GenerateRestoreSQL — regenerate the backup item instead of reusing stale positions.
- Normalize line endings and recompute positions if the text was edited or migrated between systems.
- Confirm the statement at the position is an UPDATE or DELETE; convert the restore flow to handle only supported DML.
- 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
- Regenerate backup item positions whenever statement text changes.
- Normalize line endings (LF vs CRLF) before computing or comparing positions.
- Never restore INSERT/MERGE statements through this path — only UPDATE/DELETE.
- Pin parser version so recorded positions match the parsing engine used at restore time.
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
- failed to extract single SQL: %v
- failed to prepare transformation
- no original SQL
- failed to get source database ID for %s
- failed to get target database ID for %s
AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06).
Data as JSON: /api/errors/293bfd34afc0a9ea.
Report an issue: GitHub.