gastownhall/beads · warning

db: LabelSQLRepository.DeleteAllForIDs rows affected

Error message

db: LabelSQLRepository.DeleteAllForIDs rows affected: %w

What it means

LabelSQLRepository.DeleteAllForIDs executed the DELETE but res.RowsAffected() returned an error, so the number of deleted rows could not be determined. This is a driver capability/transport issue, not a failed delete.

Solutions

  1. Unwrap the error to see which driver call failed
  2. Check the storage driver supports RowsAffected for DML (upgrade driver if not)
  3. If using a mock/test runner, implement RowsAffected in the stub result
  4. Treat total as best-effort only if your use case does not need exact counts (may require upstream change)

Example fix

// before
// stub result without RowsAffected
type fakeResult struct{}
// after
type fakeResult struct{ n int64 }
func (f fakeResult) RowsAffected() (int64, error) { return f.n, nil }
func (f fakeResult) LastInsertId() (int64, error) { return 0, nil }
Defensive patterns

Strategy: fallback

Validate before calling

// prefer drivers known to support RowsAffected; avoid stub runners in prod paths

Try / catch

n, err := repo.DeleteAllForIDs(ctx, ids, opts)
if err != nil && strings.Contains(err.Error(), "rows affected") {
    // delete likely succeeded; treat count as unknown
    log.Printf("delete ok, count unknown: %v", err)
    return nil
}

Prevention

When it happens

Trigger: Calling DeleteAllForIDs against a driver/connection that does not support or fails to report rows affected (e.g. some drivers return driver.ErrSkip or transport errors).

Common situations: Using a driver or proxy that does not implement RowsAffected; connection interruption right after statement execution; mock/stub runners in tests lacking RowsAffected support.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/6732378f16b86236. Report an issue: GitHub.

Appendix: source

Thrown at internal/storage/domain/db/label.go:222

		placeholders := make([]string, len(batch))
		args := make([]any, len(batch))
		for i, id := range batch {
			placeholders[i] = "?"
			args[i] = id
		}
		//nolint:gosec // G201: table is one of two hardcoded constants; ? placeholders only.
		res, err := r.runner.ExecContext(ctx,
			fmt.Sprintf("DELETE FROM %s WHERE issue_id IN (%s)", table, strings.Join(placeholders, ",")),
			args...)
		if err != nil {
			if opts.UseWispsTable && dberrors.IsTableNotExist(err) {
				return total, nil
			}
			return total, fmt.Errorf("db: LabelSQLRepository.DeleteAllForIDs from %s: %w", table, err)
		}
		n, err := res.RowsAffected()
		if err != nil {
			return total, fmt.Errorf("db: LabelSQLRepository.DeleteAllForIDs rows affected: %w", err)
		}
		total += int(n)
	}
	return total, nil
}

func (r *labelSQLRepositoryImpl) CountAllForIDs(ctx context.Context, ids []string, opts domain.LabelOpts) (int, error) {
	if len(ids) == 0 {
		return 0, nil
	}
	table := "labels"
	if opts.UseWispsTable {
		table = "wisp_labels"
	}
	count, err := issueops.CountRowsForIssueIDsInTx(ctx, r.runner, table, ids)
	if err != nil {
		if opts.UseWispsTable && dberrors.IsTableNotExist(err) {
			return 0, nil

View on GitHub (pinned to 71377f2769)