{"record":{"id":"293bfd34afc0a9ea","repo":"bytebase/bytebase","slug":"could-not-find-statement-at-position-line-d-d-293bfd","errorCode":null,"errorMessage":"could not find statement at position (line %d:%d - %d:%d)","messagePattern":"could not find statement at position \\(line (.+?):(.+?) - (.+?):(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/parser/tsql/restore.go","lineNumber":55,"sourceCode":"\t\treturn \"\", err\n\t}\n\n\t// Find the AST that contains the statement at the backup position\n\tvar targetResult ast.Node\n\tfor _, parsedStatement := range parsedStatements {\n\t\tnode, ok := GetOmniNode(parsedStatement.AST)\n\t\tif !ok || node == nil || !isRestorableDML(node) {\n\t\t\tcontinue\n\t\t}\n\t\tstart, end := statementPositions(parsedStatement.Start, parsedStatement.Text, dmlNodeLoc(node))\n\t\tif inRange(start, end, backupItem.StartPosition, backupItem.EndPosition) {\n\t\t\ttargetResult = node\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetResult == nil {\n\t\treturn \"\", errors.Errorf(\"could not find statement at position (line %d:%d - %d:%d)\",\n\t\t\tbackupItem.StartPosition.Line, backupItem.StartPosition.Column,\n\t\t\tbackupItem.EndPosition.Line, backupItem.EndPosition.Column)\n\t}\n\n\tsqlForComment, truncated := common.TruncateString(originalSQL, maxCommentLength)\n\tif truncated {\n\t\tsqlForComment += \"...\"\n\t}\n\treturn doGenerate(ctx, rCtx, sqlForComment, targetResult, backupItem)\n}\n\nfunc doGenerate(ctx context.Context, rCtx base.RestoreContext, sqlForComment string, node ast.Node, backupItem *storepb.PriorBackupDetail_Item) (string, error) {\n\t_, _, sourceDatabase, err := common.GetDatabaseResourceName(backupItem.SourceTable.Database)\n\tif err != nil {\n\t\treturn \"\", errors.Wrapf(err, \"failed to get source database ID for %s\", backupItem.SourceTable.Database)\n\t}\n\t_, _, targetDatabase, err := common.GetDatabaseResourceName(backupItem.TargetTable.Database)\n\tif err != nil {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/parser/tsql/restore.go#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n// stale positions from an older text version\nbackupItem.StartPosition = &storepb.Position{Line: 3, Column: 5}\n// after\n// recompute positions from current text\nstart, end := statementPositions(parsed.Start, parsed.Text, dmlNodeLoc(node))\nbackupItem.StartPosition, backupItem.EndPosition = start, end","handlingStrategy":"validation","validationCode":"// recompute positions from the same text passed to GenerateRestoreSQL and compare\nstart, end := statementPositions(parsed.Start, parsed.Text, dmlNodeLoc(node))\nif !inRange(start, end, backupItem.StartPosition, backupItem.EndPosition) {\n    return errors.New(\"stale backup item positions; regenerate against current text\")\n}","typeGuard":null,"tryCatchPattern":"sql, err := tsql.GenerateRestoreSQL(ctx, rCtx, statement, backupItem)\nif err != nil && strings.HasPrefix(err.Error(), \"could not find statement at position\") {\n    // positions are stale: rebuild the backup item from the current text\n}","preventionTips":["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."],"tags":["tsql","restore","position-mismatch","parsing"],"backgroundTag":"resource-not-found","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}