{"record":{"id":"9252d51a849fce5d","repo":"Billionmail/BillionMail","slug":"failed-to-query-existing-abnormal-recipients-w","errorCode":null,"errorMessage":"Failed to query existing abnormal recipients: %w","messagePattern":"Failed to query existing abnormal recipients: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/abnormal_recipient/abnormal_recipient.go","lineNumber":100,"sourceCode":"\tvar recipient entity.AbnormalRecipient\n\terr := g.DB().Model(\"abnormal_recipient\").Where(\"id\", id).Scan(&recipient)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to get exception recipient: %w\", err)\n\t}\n\treturn &recipient, nil\n}\n\nfunc BatchUpsertAbnormalRecipients(ctx context.Context, recipients []string, addType int, description string) error {\n\tnow := time.Now().Unix()\n\n\tif len(recipients) == 0 {\n\t\treturn nil\n\t}\n\n\tvar existList []entity.AbnormalRecipient\n\terr := g.DB().Model(\"abnormal_recipient\").WhereIn(\"recipient\", recipients).Scan(&existList)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to query existing abnormal recipients: %w\", err)\n\t}\n\texistMap := make(map[string]*entity.AbnormalRecipient)\n\tfor _, r := range existList {\n\t\texistMap[r.Recipient] = &r\n\t}\n\t// 1. Update the existing one count+1\n\tfor _, r := range existList {\n\t\t_, err := g.DB().Model(\"abnormal_recipient\").Where(\"id\", r.Id).Data(g.Map{\n\t\t\t\"count\":       r.Count + 1,\n\t\t\t\"description\": description,\n\t\t\t\"add_type\":    addType,\n\t\t}).Update()\n\t\tif err != nil {\n\n\t\t\treturn fmt.Errorf(\"Failed to update abnormal recipient: %w\", err)\n\t\t}\n\n\t}","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/abnormal_recipient/abnormal_recipient.go#L82-L118","documentation":"BatchUpsertAbnormalRecipients first SELECTs existing rows with WhereIn(\"recipient\", recipients) to decide update-vs-insert. If that query fails, the error is wrapped as this message and the whole batch op aborts before any writes.","triggerScenarios":"Calling BatchUpsertAbnormalRecipients with a dead/unconfigured DB connection, missing abnormal_recipient table, or an oversized WhereIn list hitting driver/statement limits.","commonSituations":"Bulk imports during a postgres restart; fresh environment without migrations; very large recipient batches causing query timeouts.","solutions":["Verify DB connectivity and schema (migrations).","Chunk large recipient slices (e.g. 500 per batch) to avoid huge WHERE IN clauses.","Retry transient connection errors with backoff.","Inspect the wrapped pq/pgx error for the exact SQL failure."],"exampleFix":"// before\nerr := abnormal_recipient.BatchUpsertAbnormalRecipients(ctx, allRecipients, 1, \"Manually added\")\n// after\nfor chunk := range lo.Chunk(allRecipients, 500) {\n\tif err := abnormal_recipient.BatchUpsertAbnormalRecipients(ctx, chunk, 1, \"Manually added\"); err != nil {\n\t\treturn err\n\t}\n}","handlingStrategy":"retry","validationCode":"// before calling the batch upsert\nif len(recipients) == 0 { return nil }\nif err := g.DB().PingMaster(); err != nil { return fmt.Errorf(\"db unavailable: %w\", err) }","typeGuard":null,"tryCatchPattern":"err := abnormal_recipient.BatchUpsertAbnormalRecipients(ctx, recs, addType, desc)\nif err != nil {\n\tif isTransientDBError(err) { err = retry.Do(3, backoff, func() error { return abnormal_recipient.BatchUpsertAbnormalRecipients(ctx, recs, addType, desc) }) }\n\tif err != nil { return err }\n}","preventionTips":["Ping the DB before large batch jobs","Chunk recipient lists to <1000 entries","Ensure migrations run on deploy"],"tags":["database","goframe","query-failure"],"backgroundTag":"database-query-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}