Skip to content

feat: Optimize PG source_kind, relation deletion using indices - BED-8832 - #101

Open
StephenHinck wants to merge 8 commits into
mainfrom
BED-8832
Open

feat: Optimize PG source_kind, relation deletion using indices - BED-8832#101
StephenHinck wants to merge 8 commits into
mainfrom
BED-8832

Conversation

@StephenHinck

@StephenHinck StephenHinck commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Introduces new, optional functions for deleting nodes and relationships by kind, enabling the use of indices for more performant bulk deletes. This primarily supports the various database management capabilities built into BloodHound, but may be used for other bulk delete operations as necessary.

Implemented in SpecterOps/BloodHound#2942

Resolves: BED-8832

Type of Change

  • Chore (a change that does not modify the application functionality)
  • Bug fix (a change that fixes an issue)
  • New feature / enhancement (a change that adds new functionality)
  • Refactor (no behaviour change)
  • Test coverage
  • Build / CI / tooling
  • Documentation

Testing

  • Unit tests added / updated
  • Integration tests added / updated
  • Full test suite run (make test_all with CONNECTION_STRING set)

Screenshots (if appropriate):

Driver Impact

  • PostgreSQL driver (drivers/pg)
  • Neo4j driver (drivers/neo4j)

Checklist

  • Code is formatted
  • All existing tests pass
  • go.mod / go.sum are up to date if dependencies changed

Summary by CodeRabbit

  • New Features
    • Added PostgreSQL server-side bulk deletion for nodes by included or excluded kinds, with incident relationship cleanup.
    • Added PostgreSQL server-side bulk deletion for relationships by kind.
  • Bug Fixes
    • Improved safety for kind-filtered deletes: undefined/empty include kinds become a no-op, while undefined exclude kinds fail closed.
  • Tests
    • Added unit tests for delete SQL generation, kind-ID resolution caching behavior, and empty-input no-op handling.
    • Added manual PostgreSQL integration tests for node and relationship deletion by kind.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds PostgreSQL server-side deletion by node or relationship kind, including cached kind-ID resolution, parameterized SQL generation, undefined-kind handling, and unit/manual integration tests for filtering, cascades, and no-op behavior.

Changes

PostgreSQL Bulk Delete by Kind

Layer / File(s) Summary
Kind resolution and bulk-delete methods
drivers/pg/driver.go
Resolves cached kind IDs and adds parameterized node and relationship deletion operations.
PG driver unit tests
drivers/pg/driver_test.go
Tests SQL predicate construction, cached resolution, and empty relationship-delete inputs.
Node deletion integration coverage
integration/pgsql_delete_by_kind_test.go
Verifies include/exclude deletion, cascade behavior, undefined-kind handling, and cleanup.
Relationship deletion integration coverage
integration/pgsql_delete_relationships_by_kind_test.go
Verifies single-kind, multi-kind, undefined-kind, and nil-input deletion behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant PGDriver
  participant KindCache
  participant PostgreSQL
  Caller->>PGDriver: Request deletion by kind
  PGDriver->>KindCache: Resolve graph kinds to IDs
  KindCache-->>PGDriver: Return IDs and undefined kinds
  PGDriver->>PostgreSQL: Execute parameterized DELETE
  PostgreSQL-->>PGDriver: Return result
  PGDriver-->>Caller: Return success or error
Loading

Suggested labels: go

Suggested reviewers: zinic

Poem

I’m a rabbit with SQL in my burrow,
Deleting by kind without worry or sorrow.
Nodes hop away, edges follow in flight,
Undefined kinds leave the graph just right.
Cache, query, test—what a tidy delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the main change: PostgreSQL bulk deletes by kind using index-backed operations.
Description check ✅ Passed The description matches the template and includes the purpose, ticket, change type, testing, driver impact, and checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch BED-8832

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@drivers/pg/driver.go`:
- Around line 231-254: The delete builder in the `Delete` flow is too tolerant
when resolving `excludeAny`, which can turn a protected delete into an unguarded
`delete from node`. Update the logic around `resolveKindIDs` in
`drivers/pg/driver.go` so unresolved exclude kinds fail closed instead of being
silently dropped, and make the `predicates`/`arguments` construction in the
delete statement require valid `excludeIDs` before adding the `not (kind_ids
operator ...)` clause. Keep `includeAny` behavior unchanged, but ensure
`excludeAny` cannot widen the delete when `resolveKindIDs` returns an empty or
partial match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0dd1938a-2de7-4984-b84d-79a28b13ad41

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbfba4 and 4347bb9.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • drivers/pg/driver.go
  • drivers/pg/query/sql/schema_up.sql
  • go.mod
  • integration/pgsql_delete_by_kind_test.go
  • integration/pgsql_delete_cascade_test.go
  • integration/pgsql_delete_relationships_by_kind_test.go

Comment thread drivers/pg/driver.go Outdated
@StephenHinck
StephenHinck changed the base branch from main to BED-8796 June 30, 2026 19:30
@StephenHinck
StephenHinck changed the base branch from BED-8796 to main June 30, 2026 22:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
drivers/pg/driver.go (1)

190-241: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Build-breaking syntax error: WipeGraph never closes, and resolveKindIDs is duplicated with conflicting signatures.

Line 212's }) only closes the WriteTransaction call; WipeGraph itself is left unclosed, so the resolveKindIDs doc comment and func declaration at lines 213-216 land inside WipeGraph's body — Go does not allow named function declarations nested inside another function, which is exactly the syntax error the pipeline reports at line 216. Immediately after that, lines 217-220 duplicate the same doc comment and redeclare resolveKindIDs again with a different signature (([]int16, error) at line 216 vs ([]int16, graph.Kinds, error) at line 220). Every call site (DeleteNodesByKinds, DeleteRelationshipsByKinds) uses the 3-return-value form, so line 220 is the intended final signature and lines 213-216 are stray leftovers from an unresolved merge/diff.

This won't compile as-is.

🐛 Proposed fix: close `WipeGraph` and drop the duplicate `resolveKindIDs` block
 		return nil
 	})
+}
 
-// resolveKindIDs maps kinds to their integer IDs, refreshing the schema cache once on a miss. Kinds that remain
-// undefined after the refresh are tolerated and omitted from the result so that callers match no nodes for them
-// rather than erroring.
-func (s *Driver) resolveKindIDs(ctx context.Context, kinds graph.Kinds) ([]int16, error) {
 // resolveKindIDs maps kinds to their integer IDs, refreshing the schema cache once on a miss. It returns the resolved
 // IDs alongside any kinds that remain undefined after the refresh, so callers can decide whether an unresolved kind is
 // a tolerable no-op (include predicates) or must fail closed (exclude predicates).
 func (s *Driver) resolveKindIDs(ctx context.Context, kinds graph.Kinds) ([]int16, graph.Kinds, error) {

Based on the static analysis hint flagging 216-216: syntax error: unexpected name context in argument list; possibly missing comma or ), which corroborates this exact defect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@drivers/pg/driver.go` around lines 190 - 241, Close WipeGraph after the
WriteTransaction call so resolveKindIDs is declared at file scope. Remove the
stray duplicate resolveKindIDs declaration and its duplicate comment, retaining
the three-return-value signature used by DeleteNodesByKinds and
DeleteRelationshipsByKinds.

Source: Linters/SAST tools

🧹 Nitpick comments (1)
drivers/pg/driver.go (1)

269-277: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated acquire/exec/error-wrap pattern across the two delete methods.

DeleteNodesByKinds and DeleteRelationshipsByKinds both acquire a pooled connection, defer-release it, execute, and wrap the error with "%s: %w". Consider extracting a small helper (e.g. execDelete(ctx, statement, args...)) to avoid the duplication as more kind-based bulk-delete methods are added.

Also applies to: 328-336

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@drivers/pg/driver.go` around lines 269 - 277, Extract the duplicated pool
acquire, deferred release, execution, and error-wrapping logic from
DeleteNodesByKinds and DeleteRelationshipsByKinds into a shared helper such as
execDelete. Update both delete methods to delegate to that helper while
preserving their existing statement, arguments, and error messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@integration/pgsql_delete_relationships_by_kind_test.go`:
- Around line 131-143: Update the “undefined and empty kinds are a safe no-op”
test to also call DeleteRelationshipsByKinds with a non-nil empty graph.Kinds{}
value, while retaining the existing nil and missing-kind cases and asserting no
error.

---

Outside diff comments:
In `@drivers/pg/driver.go`:
- Around line 190-241: Close WipeGraph after the WriteTransaction call so
resolveKindIDs is declared at file scope. Remove the stray duplicate
resolveKindIDs declaration and its duplicate comment, retaining the
three-return-value signature used by DeleteNodesByKinds and
DeleteRelationshipsByKinds.

---

Nitpick comments:
In `@drivers/pg/driver.go`:
- Around line 269-277: Extract the duplicated pool acquire, deferred release,
execution, and error-wrapping logic from DeleteNodesByKinds and
DeleteRelationshipsByKinds into a shared helper such as execDelete. Update both
delete methods to delegate to that helper while preserving their existing
statement, arguments, and error messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37f36512-cb99-4af5-b181-66e25a7b617f

📥 Commits

Reviewing files that changed from the base of the PR and between 47c2bde and 4902d16.

📒 Files selected for processing (4)
  • drivers/pg/driver.go
  • drivers/pg/driver_test.go
  • integration/pgsql_delete_by_kind_test.go
  • integration/pgsql_delete_relationships_by_kind_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • integration/pgsql_delete_by_kind_test.go
  • drivers/pg/driver_test.go

Comment thread integration/pgsql_delete_relationships_by_kind_test.go
@StephenHinck

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

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.

1 participant