-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathengine.go
More file actions
167 lines (142 loc) · 4.85 KB
/
engine.go
File metadata and controls
167 lines (142 loc) · 4.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package expr
import (
"context"
"sync"
"github.com/cespare/xxhash/v2"
"github.com/google/uuid"
)
type EngineType int
const (
EngineTypeNone = iota
EngineTypeStringHash
EngineTypeNullMatch
EngineTypeBTree
// EngineTypeART
)
// matchKey is a composite key combining evalID and groupID
type matchKey struct {
evalID uuid.UUID
groupID groupID
}
var matchResultPool = sync.Pool{
New: func() any {
return &MatchResult{
Result: make(map[matchKey]int),
}
},
}
func NewMatchResult() *MatchResult {
return matchResultPool.Get().(*MatchResult)
}
// MatchResult stores matches as a pooled map
type MatchResult struct {
Result map[matchKey]int // (evalID, groupID) -> count
}
// Release returns the MatchResult to the pool
func (m *MatchResult) Release() {
clear(m.Result)
matchResultPool.Put(m)
}
func (m *MatchResult) Len() int {
return len(m.Result)
}
// Add increments the count for the given (evalID, groupID)
func (m *MatchResult) Add(evalID uuid.UUID, gID groupID) {
m.Result[matchKey{evalID: evalID, groupID: gID}]++
}
// AddExprs increments counts for each expression part
func (m *MatchResult) AddExprs(exprs ...*StoredExpressionPart) {
for _, expr := range exprs {
m.Result[matchKey{evalID: expr.EvaluableID, groupID: expr.GroupID}]++
}
}
// GroupMatches returns the count for a given eval's group ID
func (m *MatchResult) GroupMatches(evalID uuid.UUID, gID groupID) int {
return m.Result[matchKey{evalID: evalID, groupID: gID}]
}
// MatchingEngine represents an engine (such as a b-tree, radix trie, or
// simple hash map) which matches a predicate over many expressions.
type MatchingEngine interface {
// Type returns the EngineType
Type() EngineType
// Match takes an input event, containing key:value pairs of data, and
// matches the given data to any ExpressionParts stored in the engine.
//
// Each implementation of the engine may differ on granularity of
// expression parts received. Some may return false positives, but
// each MatchingEngine should NEVER omit ExpressionParts which match
// the given input.
//
// Note that the MatchResult is mutated on input.
Match(ctx context.Context, input map[string]any, result *MatchResult) (err error)
// Add adds a new expression part to the matching engine for future matches.
Add(ctx context.Context, p ExpressionPart) error
// Remove removes multiple expression parts in a single batch operation.
// Returns the number of parts successfully processed before any timeout/cancellation.
Remove(ctx context.Context, parts []ExpressionPart) (int, error)
// Search searches for a given variable<>value match, returning any expression
// parts that match.
//
// Similar to match, each implementation of the engine may differ on
// granularity of expression parts received. Some may return false positives by
// ignoring the variable name. Note that each MatchingEngine should NEVER
// omit ExpressionParts which match the given input; false positives are okay,
Search(ctx context.Context, variable string, input any, result *MatchResult)
// but not returning valid matches must be impossible.
}
// Leaf represents the leaf within a tree. This stores all expressions
// which match the given expression.
//
// For example, adding two expressions each matching "event.data == 'foo'"
// in an ART creates a leaf node with both evaluable expressions stored
// in Evals
//
// Note that there are many sub-clauses which need to be matched. Each
// leaf is a subset of a full expression. Therefore,
type Leaf struct {
Evals []*ExpressionPart
}
// ExpressionPart represents a predicate group which is part of an expression.
// All parts for the given group ID must evaluate to true for the predicate to
// be matched.
type ExpressionPart struct {
// GroupID represents a group ID for the expression part.
//
// Within an expression, multiple predicates may be chained with &&. Each
// of these must evaluate to `true` for an expression to match. Group IDs
// are shared amongst each predicate within an expression.
//
// This lets us determine whether the entire group has been matched.
GroupID groupID
Predicate *Predicate
Parsed *ParsedExpression
}
func (p ExpressionPart) Hash() uint64 {
return xxhash.Sum64String(p.Predicate.String())
}
func (p ExpressionPart) EqualsStored(n *StoredExpressionPart) bool {
if p.GroupID != n.GroupID {
return false
}
return p.Hash() == n.PredicateID
}
func (p ExpressionPart) ToStored() *StoredExpressionPart {
var id uuid.UUID
if p.Parsed != nil {
id = p.Parsed.EvaluableID
}
return &StoredExpressionPart{
EvaluableID: id,
GroupID: p.GroupID,
PredicateID: p.Hash(),
Ident: &p.Predicate.Ident,
}
}
// StoredExpressionPart is a lightweight expression part which only stores
// a hash of the predicate to reduce memory usage.
type StoredExpressionPart struct {
EvaluableID uuid.UUID
GroupID groupID
PredicateID uint64
Ident *string
}