{"record":{"id":"eb385f3b3f87e1ed","repo":"vitessio/vitess","slug":"cannot-update-progress-with-a-nil-diff-report","errorCode":null,"errorMessage":"cannot update progress with a nil diff report","messagePattern":"cannot update progress with a nil diff report","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vttablet/tabletmanager/vdiff/table_differ.go","lineNumber":791,"sourceCode":"\t\t// If the collation is nil or unknown, use binary collation to compare as bytes.\n\t\tcollationID = col.collation\n\t\tif collationID == collations.Unknown {\n\t\t\tcollationID = collations.CollationBinaryID\n\t\t}\n\t\tc, err = evalengine.NullsafeCompare(sourceRow[compareIndex], targetRow[compareIndex], td.wd.collationEnv, collationID, nil)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif c != 0 {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\treturn 0, nil\n}\n\nfunc (td *tableDiffer) updateTableProgress(dbClient binlogplayer.DBClient, dr *DiffReport, lastRow []sqltypes.Value) error {\n\tif dr == nil {\n\t\treturn errors.New(\"cannot update progress with a nil diff report\")\n\t}\n\n\tvar err error\n\tvar query string\n\trpt, err := json.Marshal(dr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif lastRow == nil {\n\t\tquery, err = sqlparser.ParseAndBind(sqlUpdateTableNoProgress,\n\t\t\tsqltypes.Int64BindVariable(dr.ProcessedRows),\n\t\t\tsqltypes.StringBindVariable(string(rpt)),\n\t\t\tsqltypes.Int64BindVariable(td.wd.ct.id),\n\t\t\tsqltypes.StringBindVariable(td.table.Name),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err","sourceCodeStart":773,"sourceCodeEnd":809,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vttablet/tabletmanager/vdiff/table_differ.go#L773-L809","documentation":"tableDiffer.updateTableProgress is invoked with dr (*DiffReport) = nil while trying to persist per-table vdiff progress. Progress updates require a valid diff report to marshal to JSON and write to the _vt.vreplication message/progress state, so a nil report is rejected explicitly instead of panicking later in json.Marshal.","triggerScenarios":"The differ's diff() loop (or tests) calls updateTableProgress with a nil DiffReport pointer, typically when no report was produced for the table or a nil is passed from an anonymous helper.","commonSituations":"A vdiff run where the table produced no comparison result before a progress update was attempted; programming changes in custom/test code (TestUpdateTableProgressDropsQueryEcho) calling updateTableProgress directly.","solutions":["Ensure the diff pass always produces a non-nil DiffReport before calling updateTableProgress","Guard the caller: skip the progress update when dr == nil instead of calling it","Check for early-return paths in diff() that leave the report nil (e.g. zero rows compared) and set a default empty report"],"exampleFix":"// before\ntd.updateTableProgress(dbClient, dr, lastRow)\n// after\nif dr != nil {\n    td.updateTableProgress(dbClient, dr, lastRow)\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func hasDiffReport(dr *DiffReport) bool { return dr != nil }\nif !hasDiffReport(dr) { skipProgressUpdate() }","tryCatchPattern":"if err := td.updateTableProgress(dbClient, dr, lastRow); err != nil {\n    log.Warn(\"progress update failed\", slog.Any(\"error\", err))\n}","preventionTips":["Never call updateTableProgress without a populated DiffReport","Check early-return paths in diff() that can leave the report nil","Add nil-report tests when touching tableDiffer"],"tags":["vdiff","nil-pointer","progress"],"backgroundTag":"nil-diff-report","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}