|
1
|
|
|
package context |
|
2
|
|
|
|
|
3
|
|
|
import ( |
|
4
|
|
|
"sort" |
|
5
|
|
|
|
|
6
|
|
|
"golang.org/x/exp/slices" |
|
7
|
|
|
|
|
8
|
|
|
"github.com/Permify/permify/internal/storage/context/utils" |
|
9
|
|
|
"github.com/Permify/permify/pkg/database" |
|
10
|
|
|
base "github.com/Permify/permify/pkg/pb/base/v1" |
|
11
|
|
|
) |
|
12
|
|
|
|
|
13
|
|
|
// ContextualTuples - A collection of tuples with context. |
|
14
|
|
|
type ContextualTuples struct { |
|
15
|
|
|
Tuples []*base.Tuple |
|
16
|
|
|
} |
|
17
|
|
|
|
|
18
|
|
|
// NewContextualTuples - Creates a new collection of tuples with context. |
|
19
|
|
|
func NewContextualTuples(tuples ...*base.Tuple) *ContextualTuples { |
|
20
|
|
|
return &ContextualTuples{ |
|
21
|
|
|
Tuples: tuples, |
|
22
|
|
|
} |
|
23
|
|
|
} |
|
24
|
|
|
|
|
25
|
|
|
// QueryRelationships filters the ContextualTuples based on the provided TupleFilter |
|
26
|
|
|
// and returns a TupleIterator for the filtered tuples. |
|
27
|
|
|
// QueryRelationships filters the ContextualTuples based on the provided TupleFilter, applies cursor-based pagination, and returns a TupleIterator for the filtered tuples. |
|
28
|
|
|
func (c *ContextualTuples) QueryRelationships(filter *base.TupleFilter, pagination database.CursorPagination) (*database.TupleIterator, error) { |
|
29
|
|
|
// Sort tuples based on the provided order field |
|
30
|
|
|
sort.Slice(c.Tuples, func(i, j int) bool { |
|
31
|
|
|
switch pagination.Sort() { |
|
32
|
|
|
case "entity_id": |
|
33
|
|
|
return c.Tuples[i].GetEntity().GetId() < c.Tuples[j].GetEntity().GetId() |
|
34
|
|
|
case "subject_id": |
|
35
|
|
|
return c.Tuples[i].GetSubject().GetId() < c.Tuples[j].GetSubject().GetId() |
|
36
|
|
|
default: |
|
37
|
|
|
return false // If no valid order is provided, no sorting is applied |
|
38
|
|
|
} |
|
39
|
|
|
}) |
|
40
|
|
|
|
|
41
|
|
|
cursor := "" |
|
42
|
|
|
if pagination.Cursor() != "" { |
|
43
|
|
|
t, err := utils.EncodedContinuousToken{Value: pagination.Cursor()}.Decode() |
|
44
|
|
|
if err != nil { |
|
45
|
|
|
return nil, err |
|
46
|
|
|
} |
|
47
|
|
|
cursor = t.(utils.ContinuousToken).Value |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
// Filter the tuples based on the provided filter and cursor |
|
51
|
|
|
filtered := c.filterTuples(filter, cursor, pagination.Sort()) |
|
52
|
|
|
|
|
53
|
|
|
// Return a new TupleIterator for the filtered tuples |
|
54
|
|
|
return database.NewTupleIterator(filtered...), nil |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
// filterTuples applies the provided filter to c's Tuples and returns a slice of Tuples that match the filter. |
|
58
|
|
|
func (c *ContextualTuples) filterTuples(filter *base.TupleFilter, cursor, order string) []*base.Tuple { |
|
59
|
|
|
var filtered []*base.Tuple // Initialize a slice to hold the filtered tuples |
|
60
|
|
|
|
|
61
|
|
|
// Iterate over the tuples |
|
62
|
|
|
for _, tup := range c.Tuples { |
|
63
|
|
|
// Skip tuples that come before the cursor based on the specified order field |
|
64
|
|
|
if cursor != "" && !isTupleAfterCursor(tup, cursor, order) { |
|
65
|
|
|
continue |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
// If a tuple matches the Entity, Relation, and Subject filters, add it to the filtered slice |
|
69
|
|
|
if matchesEntityFilterForTuples(tup, filter.GetEntity()) && |
|
70
|
|
|
matchesRelationFilter(tup, filter.GetRelation()) && |
|
71
|
|
|
matchesSubjectFilter(tup, filter.GetSubject()) { |
|
72
|
|
|
filtered = append(filtered, tup) |
|
73
|
|
|
} |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
return filtered // Return the filtered tuples |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
// isAfterCursor checks if the tuple's ID (based on the order field) comes after the cursor. |
|
80
|
|
|
func isTupleAfterCursor(tup *base.Tuple, cursor, order string) bool { |
|
81
|
|
|
switch order { |
|
82
|
|
|
case "entity_id": |
|
83
|
|
|
return tup.GetEntity().GetId() >= cursor |
|
84
|
|
|
case "subject_id": |
|
85
|
|
|
return tup.GetSubject().GetId() >= cursor |
|
86
|
|
|
default: |
|
87
|
|
|
// If the order field is not recognized, default to not skipping any tuples |
|
88
|
|
|
return true |
|
89
|
|
|
} |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
// matchesEntityFilterForTuples checks if a Tuple matches the conditions in an EntityFilter. |
|
93
|
|
|
func matchesEntityFilterForTuples(tup *base.Tuple, filter *base.EntityFilter) bool { |
|
94
|
|
|
// Return true if the filter is empty or the tuple's entity matches the filter |
|
95
|
|
|
return (filter.GetType() == "" || tup.GetEntity().GetType() == filter.GetType()) && |
|
96
|
|
|
(len(filter.GetIds()) == 0 || slices.Contains(filter.GetIds(), tup.GetEntity().GetId())) |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
// matchesRelationFilter checks if a Tuple matches the condition in a RelationFilter. |
|
100
|
|
|
func matchesRelationFilter(tup *base.Tuple, filter string) bool { |
|
101
|
|
|
// Return true if the filter is empty or the tuple's relation matches the filter |
|
102
|
|
|
return filter == "" || tup.GetRelation() == filter |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
// matchesSubjectFilter checks if a Tuple matches the conditions in a SubjectFilter. |
|
106
|
|
|
func matchesSubjectFilter(tup *base.Tuple, filter *base.SubjectFilter) bool { |
|
107
|
|
|
// Return true if the filter is empty or the tuple's subject matches the filter |
|
108
|
|
|
return (filter.GetType() == "" || tup.GetSubject().GetType() == filter.GetType()) && |
|
109
|
|
|
(len(filter.GetIds()) == 0 || slices.Contains(filter.GetIds(), tup.GetSubject().GetId())) && |
|
110
|
|
|
(filter.GetRelation() == "" || tup.GetSubject().GetRelation() == filter.GetRelation()) |
|
111
|
|
|
} |
|
112
|
|
|
|