Add global node filter / query restriction support
Problem
Common cases like soft delete and multi-tenancy require adding the same WHERE clause to every query. SDN has no built-in way to apply a predicate to all generated queries for an entity (derived queries, counts/exists, relationship loads). This is easy to miss and error-prone.
Proposal
Introduce an opt-in, entity-scoped restriction that SDN appends to generated Cypher.
@Node("Organization")
@QueryRestriction("deletedAt IS NULL")
@QueryRestriction("tenantId = $tenantId") // maybe even allow SpEL
class Organization { Instant deletedAt; String tenantId; }
- Support simple parameters via a small SPI:
@Bean
QueryRestrictionParameters params(TenantResolver r) {
return () -> Map.of("tenantId", r.currentTenantId());
}
- Allow per-method opt-out:
@IgnoreQueryRestrictions
@Query("MATCH (o:Organization) RETURN o")
List<Organization> adminListAll();
Scope
Applied to derived queries (findAll, findById, count, exists, paging) and relationship loads. Does not change custom @Query unless opted in/out as above.
Workarounds today
Duplicate predicates everywhere or change labels on delete—both brittle.
Prior art
Hibernate/JPA’s @SQLRestriction (and now deprecated predecessor @Where) provide similar per-entity SQL predicates.
Sorry, I just realized this is a duplicate of https://github.com/spring-projects/spring-data-neo4j/issues/1910
Building upon what has been said there I have a few ideas and suggestions, that work alongside the idea of **acknowledging “graph is the truth”, and that global filters on entity loads are risky—filtered relations could be pruned on save. Still, teams need soft-delete/tenant scoping without repeating predicates. A few read-safe, opt-in ideas: 1. Projection-only filters (read-only): Apply filters only when returning projections/DTOs, never entities. 2. Per-query filter hint + guardrails: repo.withFilter("deletedAt IS NULL")… marks results as partial and blocks save(..) unless explicitly overridden. 3. Optional MERGE-style save mode: Per entity/relationship save strategy that doesn’t delete missing, filtered-out relations. 4. Repository-level default predicate (read paths): Append a predicate to derived/pageable methods when the return type isn’t an entity. 5. Built-in soft-delete helper: A standard soft-delete facility (props/labels + fragments) for “active” reads and softDelete/restore writes.
All are opt-in and avoid changing default replacement semantics—just reduce boilerplate for common read scenarios. Happy to hear if any of these shapes are acceptable.
Thanks for the input and the idea. In contrast to a few years ago, we are not completely against this. But we need some time to see what options we have today and how we could create a solid experience when it comes to writing entities loaded this way.