{"record":{"id":"4f9d12b08e962185","repo":"gastownhall/beads","slug":"db-labelsqlrepository-delete-s-s-rows-affected","errorCode":null,"errorMessage":"db: LabelSQLRepository.Delete %s/%s: rows affected: %w","messagePattern":"db: LabelSQLRepository\\.Delete (.+?)/(.+?): rows affected: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/label.go","lineNumber":108,"sourceCode":"func (r *labelSQLRepositoryImpl) Delete(ctx context.Context, issueID, label, actor string, opts domain.LabelOpts) error {\n\tif issueID == \"\" {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete: issueID must not be empty\")\n\t}\n\tif label == \"\" {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete: label must not be empty\")\n\t}\n\ttable := pickLabelTable(opts.UseWispsTable)\n\t//nolint:gosec // G201: table is one of two hardcoded constants\n\tresult, err := r.runner.ExecContext(ctx,\n\t\tfmt.Sprintf(\"DELETE FROM %s WHERE issue_id = ? AND label = ?\", table),\n\t\tissueID, label,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete %s/%s: %w\", issueID, label, err)\n\t}\n\trows, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Delete %s/%s: rows affected: %w\", issueID, label, err)\n\t}\n\tif rows == 0 {\n\t\treturn nil\n\t}\n\tif err := r.events.Record(ctx, domain.Event{\n\t\tIssueID:  issueID,\n\t\tType:     types.EventLabelRemoved,\n\t\tActor:    actor,\n\t\tOldValue: label,\n\t}, domain.RecordEventOpts{UseWispsTable: opts.UseWispsTable}); err != nil {\n\t\treturn err\n\t}\n\treturn issueops.RecordEventInTx(ctx, r.runner, issueops.EventUpdate, issueID, actor)\n}\n\nfunc (r *labelSQLRepositoryImpl) List(ctx context.Context, issueID string, opts domain.LabelOpts) ([]string, error) {\n\tif issueID == \"\" {\n\t\treturn nil, fmt.Errorf(\"db: LabelSQLRepository.List: issueID must not be empty\")","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/label.go#L90-L126","documentation":"Thrown when result.RowsAffected() fails after the DELETE statement in LabelSQLRepository.Delete. Without the affected-row count the repository cannot decide whether to journal the label-removed event, so it aborts with the driver error wrapped.","triggerScenarios":"Delete called on a connection whose driver errors retrieving affected rows after DELETE (stale connection, driver limitation, or mock result in tests lacking RowsAffected).","commonSituations":"Pooled connection died between ExecContext and RowsAffected; test doubles returning sql.Result implementations without RowsAffected support; driver bug in an old version.","solutions":["Retry the delete after reconnecting","Update/verify the Dolt SQL driver version","Fix test mocks to implement RowsAffected properly","Check connection pool health settings"],"exampleFix":"// before\ndb.SetMaxOpenConns(10) // stale connections linger\n// after\ndb.SetConnMaxLifetime(30 * time.Second)\ndb.SetConnMaxIdleTime(1 * time.Minute)","handlingStrategy":"retry","validationCode":"if err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"db unavailable: %w\", err) }","typeGuard":null,"tryCatchPattern":"err := repo.Delete(ctx, issueID, label, actor, opts)\nfor i := 0; i < 3 && err != nil; i++ {\n    time.Sleep(backoff(i))\n    err = repo.Delete(ctx, issueID, label, actor, opts)\n}","preventionTips":["Set ConnMaxLifetime/ConnMaxIdleTime on the pool","Retry idempotent deletes on transient errors","Implement RowsAffected correctly in test mocks","Keep the SQL driver current"],"tags":["database","rows-affected","driver"],"backgroundTag":"rows-affected-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}