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
|
|
|
|