Skip to content

feat: support hard delete#79

Merged
ren0503 merged 1 commit intomasterfrom
feat/ren/78-support-hard-delete
Sep 13, 2025
Merged

feat: support hard delete#79
ren0503 merged 1 commit intomasterfrom
feat/ren/78-support-hard-delete

Conversation

@ren0503
Copy link
Contributor

@ren0503 ren0503 commented Sep 13, 2025

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Sep 13, 2025

Summary by CodeRabbit

  • New Features
    • Deletion operations now support an optional flag to perform hard deletes in addition to existing soft-delete behavior.
    • Bulk and single-item deletions can be forced to permanently remove records when needed.
  • Chores
    • Expanded developer tasks: audit, format, lint, tidy, test, coverage (with HTML report), and benchmark for improved project maintenance.
  • Tests
    • Updated test suite to verify soft vs. hard delete behavior across single and bulk deletion scenarios.

Walkthrough

Adds new Makefile targets for auditing, formatting, linting, tidying, testing, coverage, and benchmarking. Modifies repository delete methods to accept an optional force-delete flag enabling hard deletes. Updates tests to verify soft vs hard delete behavior and seeds data via batch creation.

Changes

Cohort / File(s) Summary
Build & Tooling
Makefile
Replaced single test-coverage target with distinct targets: audit, format, lint, tidy, test, coverage (writes coverage.out/html), and benchmark. Removed old test-coverage; moved go tool cover into coverage target.
Repository Deletion API
mutation.go
Added variadic isForceDelete to DeleteOne, DeleteByID, DeleteMany. Soft-delete remains default; when forced, uses unscoped deletion and queries with WithDeleted as needed.
Tests for Deletion Semantics
mutation_test.go
Expanded tests to assert soft vs hard delete paths for single and bulk deletions. Introduced batch seeding, WithDeleted lookups, and hard-delete verification.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor C as Caller
  participant R as Repository[M]
  participant DB as DB (ORM)

  rect rgb(245,245,255)
    note over C,R: Soft delete (default)
    C->>R: DeleteByID(id)
    R->>DB: Find (scoped)
    DB-->>R: Record
    R->>DB: Delete (scoped/soft)
    DB-->>R: OK
    R-->>C: nil/error
  end

  rect rgb(245,255,245)
    note over C,R: Hard delete (force)
    C->>R: DeleteByID(id, true)
    R->>DB: Find WithDeleted
    DB-->>R: Record
    R->>DB: Unscoped Delete (hard)
    DB-->>R: OK
    R-->>C: nil/error
  end
Loading
sequenceDiagram
  autonumber
  actor C as Caller
  participant R as Repository[M]
  participant DB as DB (ORM)

  rect rgb(255,250,240)
    note over C,R: Bulk delete
    C->>R: DeleteMany(where) / DeleteMany(where, true)
    alt soft (no force)
      R->>DB: Delete matching (scoped/soft)
    else hard (force)
      R->>DB: Unscoped Delete matching
    end
    DB-->>R: OK
    R-->>C: nil/error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A bunny taps the Makefile drum,
New targets hop—audit, test—here they come!
Soft paws first, then claws of might,
Delete or purge, by caller’s right.
Carrots counted, coverage bright—
Benchmarks race in lunar light.
Thump-thump: green fields in sight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive I cannot locate the pull request body/description in the provided input (only a raw change summary is present), so I cannot determine whether the PR's description field is related to the changeset. Please paste or update the pull request description (PR body) with a brief summary of what changed, why, and how it was tested—e.g., "Adds optional hard-delete flag to repository delete methods, updates tests to cover soft vs hard deletes, and adjusts Makefile targets"—so I can re-evaluate this check.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: support hard delete" is short, follows conventional commit style, and directly reflects the primary change in the diff (adding optional hard-delete behavior to repository delete methods and tests), so it accurately summarizes the main purpose for reviewers scanning history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ren/78-support-hard-delete

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ren0503 ren0503 added this to the SQL ORM Release v2.4.0 milestone Sep 13, 2025
@ren0503 ren0503 linked an issue Sep 13, 2025 that may be closed by this pull request
@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
Makefile (3)

29-31: Use a clearer -run filter for benchmarks

-run=^Benchmark_$ is a test filter and is a bit confusing. Prefer -run=^$ to skip tests explicitly.

 benchmark:
-	go test ./... -benchmem -bench=. -run=^Benchmark_$
+	go test ./... -benchmem -bench=. -run=^$

1-5: Pin tool versions for reproducibility

@latest and relying on host-installed linters make CI non-deterministic. Pin versions or expose variables.

Minimal changes:

 audit:
 	go mod verify
 	go vet ./...
-	go run golang.org/x/vuln/cmd/govulncheck@latest ./...
+	go run golang.org/x/vuln/cmd/govulncheck@v1.0.0 ./...

Add near the top (outside this hunk) to make tools configurable:

# Tool versions (override in CI)
GOVULNCHECK_VERSION ?= v1.0.0
GOLANGCI_LINT ?= golangci-lint

# Then use:
# go run golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) ./...
# $(GOLANGCI_LINT) run

29-31: Add conventional phony targets "all" and "clean"

Also addresses checkmake warnings and improves DX.

+.PHONY: all clean
+all: tidy format lint test
+
+clean:
+	go clean -testcache
+	rm -rf coverage
mutation.go (2)

53-61: Simplify the force-delete flag handling

Minor readability tweak: compute the flag once and reuse.

-func (repo *Repository[M]) DeleteOne(where interface{}, isForceDelete ...bool) error {
-	withDeleted := false
-	if len(isForceDelete) > 0 && isForceDelete[0] {
-		withDeleted = true
-	}
+func (repo *Repository[M]) DeleteOne(where interface{}, isForceDelete ...bool) error {
+	force := len(isForceDelete) > 0 && isForceDelete[0]
 
-	record, err := repo.FindOne(where, FindOneOptions{
-		WithDeleted: withDeleted,
+	record, err := repo.FindOne(where, FindOneOptions{
+		WithDeleted: force,
 	})
@@
-	tx := repo.DB
-	if withDeleted {
+	tx := repo.DB
+	if force {
 		tx = tx.Unscoped()
 	}

Also applies to: 68-71


83-100: Global DeleteMany(nil, true) is powerful—consider guardrails

Bypassing GORM’s global-delete protection with Where("1 = 1") enables full-table soft/hard deletes. This is intentional per tests, but risky for consumers.

Options:

  • Require an explicit sentinel for “all rows” instead of nil.
  • Gate “delete all” behind config/env (e.g., AllowGlobalDelete), or log a prominent warning.
  • Document prominently in README with examples.
mutation_test.go (2)

178-196: Minor var shadowing and consistency nits

Inside the closure you re-declare err (err := repo.DeleteMany(...)) after using it above. Not harmful but can be avoided; also prefer any over interface{} for consistency with the library.

-		err := repo.DeleteMany(map[string]interface{}{"name": "abc"})
+		err = repo.DeleteMany(map[string]any{"name": "abc"})

196-217: Prefer NoError over Nil for error assertions

Slightly clearer intent.

-		require.Nil(t, err)
+		require.NoError(t, err)
@@
-		require.Nil(t, err)
+		require.NoError(t, err)
@@
-		require.Nil(t, err)
+		require.NoError(t, err)
@@
-		require.Nil(t, err)
+		require.NoError(t, err)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9114b56 and 7108740.

📒 Files selected for processing (3)
  • Makefile (1 hunks)
  • mutation.go (1 hunks)
  • mutation_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
mutation_test.go (3)
query.go (2)
  • FindOneOptions (9-15)
  • FindOptions (17-26)
model.go (1)
  • Model (11-16)
repository.go (1)
  • Repository (20-22)
mutation.go (2)
repository.go (1)
  • Repository (20-22)
query.go (1)
  • FindOneOptions (9-15)
🪛 checkmake (0.2.2)
Makefile

[warning] 29-29: Missing required phony target "all"

(minphony)


[warning] 29-29: Missing required phony target "clean"

(minphony)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
Makefile (1)

11-13: Make linter invocation robust

If golangci-lint isn’t on PATH, this will fail locally/CI. Either document the prerequisite or use a pinned container/binary via a configurable variable as suggested above.

Would you like a tools target to install/pin linters for CI?

mutation.go (3)

53-61: Confirm policy for DeleteOne(nil, …)

where == nil would delete “the first” matching record (since FindOne(nil, …) is allowed elsewhere). That’s a dangerous footgun.

Do we want to disallow DeleteOne(nil, …) (returning an error), or require an explicit sentinel (e.g., sqlorm.All())?


68-73: Hard delete via Unscoped is correct

The Unscoped path is the right way to perform hard deletes in GORM.


79-81: Forwarding the variadic flag looks good

DeleteByID cleanly delegates to DeleteOne with the variadic flag.

mutation_test.go (1)

158-171: Nice verification of soft vs hard delete semantics

Asserting visibility with WithDeleted: true before and after DeleteByID(..., true) is solid.

Comment on lines +22 to +28
.PHONY: coverage
coverage:
go clean -testcache
go test -v ./... -covermode=count -coverpkg=./... -coverprofile coverage/coverage.out
go tool cover -html coverage/coverage.out -o coverage/coverage.html No newline at end of file
go tool cover -html coverage/coverage.out -o coverage/coverage.html

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure coverage directory exists before writing outputs

Without creating the directory first, go test -coverprofile coverage/coverage.out and go tool cover ... -o coverage/coverage.html can fail with “no such file or directory”.

Apply:

 .PHONY: coverage
 coverage:
+	mkdir -p coverage
 	go clean -testcache
 	go test -v ./... -covermode=count -coverpkg=./... -coverprofile coverage/coverage.out
 	go tool cover -html coverage/coverage.out -o coverage/coverage.html
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.PHONY: coverage
coverage:
go clean -testcache
go test -v ./... -covermode=count -coverpkg=./... -coverprofile coverage/coverage.out
go tool cover -html coverage/coverage.out -o coverage/coverage.html
\ No newline at end of file
go tool cover -html coverage/coverage.out -o coverage/coverage.html
.PHONY: coverage
coverage:
mkdir -p coverage
go clean -testcache
go test -v ./... -covermode=count -coverpkg=./... -coverprofile coverage/coverage.out
go tool cover -html coverage/coverage.out -o coverage/coverage.html
🤖 Prompt for AI Agents
In Makefile around lines 23 to 28, the target writes coverage files into the
coverage/ directory without ensuring it exists; update the target to create the
directory first (e.g., run mkdir -p coverage) before running go test and go tool
cover so the coverage output and HTML file can be written successfully.

@ren0503 ren0503 merged commit a9be490 into master Sep 13, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support hard delete with repository

2 participants