fix: support recursive attributes in lookup-entity#2763
fix: support recursive attributes in lookup-entity#2763omer-topal wants to merge 4 commits intomasterfrom
Conversation
📝 WalkthroughWalkthroughAdds detection of self-cycle tuple-set relations and implements cursor-aware recursive expansion of attribute-based permissions; extends linked-schema path-chain composition and caching for nested attribute entrances; updates tests to cover same-type and cross-type recursive attribute permission/lookups. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant EF as EntityFilter
participant LS as LinkedSchema
participant DB as Storage
participant BP as BulkEntityPublisher
Client->>EF: Check/Lookup request
EF->>LS: SelfCycleRelationsForPermission(entityType, permission)
LS-->>EF: selfCycleRelations
EF->>EF: attributeEntrance(..., selfCycleRelations)
EF->>DB: query direct attribute relations (with cursor)
DB-->>EF: direct entities (+ cursor)
EF->>BP: publish direct entities
alt selfCycleRelations exist
loop per relation
EF->>EF: expandRecursiveRelation(relation, seedIDs)
EF->>DB: queryRelations(relation, seedIDs, cursor)
DB-->>EF: related entities (+ nextCursor)
EF->>BP: publish recursive entities
end
end
BP-->>Client: aggregated results
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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)
internal/engines/entity_filter.go (1)
130-210:⚠️ Potential issue | 🟠 MajorCursor-filtered seeds can drop recursive results.
When recursion is enabled, the attribute query is cursor-paginated and the seed list only reflects the current page. Descendants with IDs after the cursor but reachable solely via pre-cursor attributes will be missed. Consider collecting seeds without the cursor and applying the cursor only at publish time.
✅ Suggested fix (collect full seeds, filter publish in-memory)
- pagination := database.NewCursorPagination(database.Cursor(request.GetCursor()), database.Sort("entity_id")) + needsRecursive := request.GetEntrance().GetType() == entrance.TargetEntrance.GetType() && len(selfCycleRelations) > 0 + pagination := database.NewCursorPagination(database.Cursor(request.GetCursor()), database.Sort("entity_id")) + cursorValue := "" + if needsRecursive { + // For recursive expansion, collect full seed set and apply cursor in-memory. + pagination = database.NewCursorPagination(database.Sort("entity_id")) + if request.GetCursor() != "" { + var err error + cursorValue, err = decodeCursorValue(request.GetCursor()) + if err != nil { + return err + } + } + } ... - if !visits.AddPublished(entity) { - continue - } - publisher.Publish(entity, &base.PermissionCheckRequestMetadata{ - SnapToken: request.GetMetadata().GetSnapToken(), - SchemaVersion: request.GetMetadata().GetSchemaVersion(), - Depth: request.GetMetadata().GetDepth(), - }, request.GetContext(), base.CheckResult_CHECK_RESULT_UNSPECIFIED) - - attributeEntityIDs = append(attributeEntityIDs, entity.GetId()) + attributeEntityIDs = append(attributeEntityIDs, entity.GetId()) + if cursorValue == "" || entity.GetId() >= cursorValue { + if !visits.AddPublished(entity) { + continue + } + publisher.Publish(entity, &base.PermissionCheckRequestMetadata{ + SnapToken: request.GetMetadata().GetSnapToken(), + SchemaVersion: request.GetMetadata().GetSchemaVersion(), + Depth: request.GetMetadata().GetDepth(), + }, request.GetContext(), base.CheckResult_CHECK_RESULT_UNSPECIFIED) + } ... - if request.GetEntrance().GetType() == entrance.TargetEntrance.GetType() && - len(selfCycleRelations) > 0 && - len(attributeEntityIDs) > 0 { + if needsRecursive && len(attributeEntityIDs) > 0 {
🤖 Fix all issues with AI agents
In `@internal/engines/entity_filter.go`:
- Around line 270-285: The loop builds a TupleFilter but leaves Subject.Relation
empty, which breaks recursive traversal for self-referential relations; update
the code that constructs the &base.TupleFilter so Subject.Relation is set by
calling the schema helper to derive the correct subject relation for the given
relation and entityType (e.g., obtain subjectRel :=
schemaHelper.DeriveSubjectRelation(relation, entityType) or the equivalent
helper method in your schema package) and assign Subject.Relation = subjectRel
(keep using EntityFilter.Type, Ids=data and Subject.Ids=currentIDs as before).
| for len(queue) > 0 { | ||
| currentIDs := queue | ||
| queue = nil | ||
|
|
||
| filter := &base.TupleFilter{ | ||
| Entity: &base.EntityFilter{ | ||
| Type: entityType, | ||
| Ids: data, | ||
| }, | ||
| Relation: relation, | ||
| Subject: &base.SubjectFilter{ | ||
| Type: entityType, | ||
| Ids: currentIDs, | ||
| Relation: "", | ||
| }, | ||
| } |
There was a problem hiding this comment.
Recursive traversal ignores subject relation.
For relations that point back to the same entity type with a subject relation (e.g., @group#member), leaving Subject.Relation empty can miss edges or over-include. Use the schema helper to derive the correct subject relation.
✅ Suggested fix (respect subject relation)
- filter := &base.TupleFilter{
+ subjectRelation := engine.graph.GetSubjectRelationForPathWalk(entityType, relation, entityType)
+ filter := &base.TupleFilter{
Entity: &base.EntityFilter{
Type: entityType,
Ids: data,
},
Relation: relation,
Subject: &base.SubjectFilter{
Type: entityType,
Ids: currentIDs,
- Relation: "",
+ Relation: subjectRelation,
},
}🤖 Prompt for AI Agents
In `@internal/engines/entity_filter.go` around lines 270 - 285, The loop builds a
TupleFilter but leaves Subject.Relation empty, which breaks recursive traversal
for self-referential relations; update the code that constructs the
&base.TupleFilter so Subject.Relation is set by calling the schema helper to
derive the correct subject relation for the given relation and entityType (e.g.,
obtain subjectRel := schemaHelper.DeriveSubjectRelation(relation, entityType) or
the equivalent helper method in your schema package) and assign Subject.Relation
= subjectRel (keep using EntityFilter.Type, Ids=data and Subject.Ids=currentIDs
as before).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2763 +/- ##
==========================================
- Coverage 82.64% 82.51% -0.13%
==========================================
Files 74 74
Lines 8125 8269 +144
==========================================
+ Hits 6714 6822 +108
- Misses 892 912 +20
- Partials 519 535 +16 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @omer-topal. * #2763 (comment) The following files were modified: * `internal/engines/entity_filter.go`
Summary by CodeRabbit