Documentation
¶
Index ¶
- Constants
- Variables
- type AggregateEvaluator
- type CELCompiler
- type EngineType
- type Evaluable
- type EvaluableLoader
- type ExpressionEvaluator
- type ExpressionPart
- type Leaf
- type LiftedArgs
- type MatchingEngine
- type Node
- type ParsedExpression
- type Predicate
- type RandomReader
- type StoredExpressionPart
- type StringExpression
- type TreeParser
Constants ¶
const ( EngineTypeNone = iota EngineTypeStringHash EngineTypeNullMatch EngineTypeBTree )
const (
OptimizeNone = 0x0
)
const ( // VarPrefix is the lifted variable name used when extracting idents from an // expression. VarPrefix = "vars" )
Variables ¶
var ( ErrEvaluableNotFound = fmt.Errorf("Evaluable instance not found in aggregator") ErrInvalidType = fmt.Errorf("invalid type for tree") ErrExpressionPartNotFound = fmt.Errorf("expression part not found") )
var (
CacheTime = time.Hour
)
Functions ¶
This section is empty.
Types ¶
type AggregateEvaluator ¶
type AggregateEvaluator interface { // Add adds an expression to the tree evaluator. This returns the ratio // of aggregate to slow parts in the expression, or an error if there was an // issue. // // Purely aggregateable expressions have a ratio of 1. // Mixed expressions return the ratio of fast:slow expressions, as a float. // Slow, non-aggregateable expressions return 0. Add(ctx context.Context, eval Evaluable) (float64, error) // Remove removes an expression from the aggregate evaluator Remove(ctx context.Context, eval Evaluable) error // Evaluate checks input data against all exrpesssions in the aggregate in an optimal // manner, only evaluating expressions when necessary (based off of tree matching). // // Note that any expressions added that cannot be evaluated optimally by trees // are evaluated every time this function is called. // // Evaluate returns all matching Evaluables, plus the total number of evaluations // executed. Evaluate(ctx context.Context, data map[string]any) ([]Evaluable, int32, error) // AggregateMatch returns all expression parts which are evaluable given the input data. AggregateMatch(ctx context.Context, data map[string]any) ([]*StoredExpressionPart, error) // Len returns the total number of aggregateable and constantly matched expressions // stored in the evaluator. Len() int // FastLen returns the number of expressions being matched by aggregated trees. FastLen() int // MixedLen returns the number of expressions being matched by aggregated trees. MixedLen() int // SlowLen returns the total number of expressions that must constantly // be matched due to non-aggregateable clauses in their expressions. SlowLen() int }
AggregateEvaluator represents a group of expressions that must be evaluated for a single event received.
An AggregateEvaluator instance exists for every event name being matched.
func NewAggregateEvaluator ¶
func NewAggregateEvaluator( parser TreeParser, eval ExpressionEvaluator, evalLoader EvaluableLoader, concurrency int64, ) AggregateEvaluator
type CELCompiler ¶
type CELCompiler interface { // Compile calls Compile on the expression, parsing and validating the AST. // This returns the AST, issues during validation, and args lifted. Compile(expr string) (*cel.Ast, *cel.Issues, LiftedArgs) // Parse calls Parse on an expression, but does not check the expression // for valid variable names etc. within the env. Parse(expr string) (*cel.Ast, *cel.Issues, LiftedArgs) }
CELCompiler represents a CEL compiler which takes an expression string and returns a CEL AST, any issues during parsing, and any lifted and replaced from the expression.
By default, *cel.Env fulfils this interface. In production, it's common to provide a caching layer on top of *cel.Env to optimize parsing, as it's the slowest part of the expression process.
func EnvCompiler ¶
func EnvCompiler(env *cel.Env) CELCompiler
EnvCompiler turns a *cel.Env into a CELParser.
func NewCachingCompiler ¶
func NewCachingCompiler(env *cel.Env, cache *ccache.Cache) CELCompiler
NewCachingCompiler returns a CELCompiler which lifts quoted literals out of the expression as variables and uses caching to cache expression parsing, resulting in improved performance when parsing expressions.
type EngineType ¶
type EngineType int
type Evaluable ¶
type Evaluable interface { // GetID returns a unique identifier for the evaluable item. If there are // two instances of the same expression, the identifier should return a unique // string for each instance of the expression (eg. for two pauses). // // It has the Get prefix to reduce collisions with implementations who expose an // ID member. GetID() uuid.UUID // GetExpression returns an expression as a raw string. // // It has the Get prefix to reduce collisions with implementations who expose an // Expression member. GetExpression() string }
Evaluable represents an evaluable expression with a unique identifier.
type EvaluableLoader ¶
EvaluableLoader returns one or more evaluable items given IDs.
type ExpressionEvaluator ¶
ExpressionEvaluator is a function which evalues an expression given input data, returning a boolean and error.
type ExpressionPart ¶
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 }
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.
func (ExpressionPart) Equals ¶
func (p ExpressionPart) Equals(n ExpressionPart) bool
func (ExpressionPart) EqualsStored ¶
func (p ExpressionPart) EqualsStored(n *StoredExpressionPart) bool
func (ExpressionPart) Hash ¶
func (p ExpressionPart) Hash() uint64
func (ExpressionPart) ToStored ¶
func (p ExpressionPart) ToStored() *StoredExpressionPart
type Leaf ¶
type Leaf struct {
Evals []*ExpressionPart
}
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 LiftedArgs ¶
type LiftedArgs interface { // Get a lifted variable argument from the parsed expression. Get(val string) (any, bool) // Return all lifted variables as a map. Map() map[string]any }
LiftedArgs represents a set of variables that have been lifted from expressions and replaced with identifiers, eg `id == "foo"` becomes `id == vars.a`, with "foo" lifted as "vars.a".
type MatchingEngine ¶
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. Match(ctx context.Context, input map[string]any) (matched []*StoredExpressionPart, err error) // Add adds a new expression part to the matching engine for future matches. Add(ctx context.Context, p ExpressionPart) error // Remove removes an expression part from the matching engine, ensuring that the // ExpressionPart will not be matched in the future. Remove(ctx context.Context, p ExpressionPart) 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, // but not returning valid matches must be impossible. Search(ctx context.Context, variable string, input any) (matched []*StoredExpressionPart) }
MatchingEngine represents an engine (such as a b-tree, radix trie, or simple hash map) which matches a predicate over many expressions.
type Node ¶
type Node struct { GroupID groupID // Ands contains predicates at this level of the expression that are joined together // with an && operator. All nodes in this set must evaluate to true in order for this // node in the expression to be truthy. // // Note that if any on of the Ors nodes evaluates to true, this node is truthy, regardless // of whether the Ands set evaluates to true. Ands []*Node `json:"and,omitempty"` // Ors represents matching OR groups within this expression. For example, in // the expression `a == b && (c == 1 || d == 1)` the top-level predicate group will // have a child group containing the parenthesis sub-expression. // // At least one of the Or node's sub-trees must evaluate to true for the node to // be truthy, alongside all Ands. Ors []*Node `json:"or,omitempty"` // Predicate represents the predicate for this node. This must evaluate to true in order // for the expression to be truthy. // // If this is nil, this is a parent container for a series of AND or Or checks. // a == b Predicate *Predicate }
PredicateGroup represents a group of predicates that must all pass in order to execute the given expression. For example, this might contain two predicates representing an expression with two operators combined with "&&".
MATCHING & EVALUATION
A node evaluates to true if ALL of the following conditions are met:
- All of the ANDS are truthy. - One or more of the ORs are truthy
In essence, if there are ANDs and ORs, the ORs are implicitly added to ANDs:
(A && (B || C))
This requres A *and* either B or C, and so we require all ANDs plus at least one node from OR to evaluate to true
func (Node) HasPredicate ¶
type ParsedExpression ¶
type ParsedExpression struct { Root Node // Vars represents rewritten literals within the expression. // // This allows us to rewrite eg. `event.data.id == "foo"` into // `event.data.id == vars.a` such that multiple different literals // share the same expression. Using the same expression allows us // to cache and skip CEL parsing, which is the slowest aspect of // expression matching. Vars LiftedArgs // Evaluable stores the original evaluable interface that was parsed. EvaluableID uuid.UUID HasMacros bool }
ParsedExpression represents a parsed CEL expression into our higher-level AST.
Expressions are simplified and canonicalized, eg. !(a == b) becomes a != b and !(b <= a) becomes (a > b).
func (ParsedExpression) RootGroups ¶
func (p ParsedExpression) RootGroups() []*Node
RootGroups returns the top-level matching groups within an expression. This is a small utility to check the number of matching groups easily.
type Predicate ¶
type Predicate struct { // Literal represents the literal value that the operator compares against. If two // variable are being compared, this is nil and LiteralIdent holds a pointer to the // name of the second variable. Literal any // Ident is the ident we're comparing to, eg. the variable. Ident string // LiteralIdent represents the second literal that we're comparing against, // eg. in the expression "event.data.a == event.data.b" this stores event.data.b LiteralIdent *string // Operator is the binary operator being used. NOTE: This always assumes that the // ident is to the left of the operator, eg "event.data.value > 100". If the value // is to the left of the operator, the operator will be switched // (ie. 100 > event.data.value becomes event.data.value < 100) Operator string }
Predicate represents a predicate that must evaluate to true in order for an expression to be considered as viable when checking an event.
This is equivalent to a CEL overload/function/macro.
func (Predicate) LiteralAsFloat64 ¶
func (Predicate) LiteralAsString ¶
type RandomReader ¶
type StoredExpressionPart ¶
type StoredExpressionPart struct { GroupID groupID PredicateID uint64 Parsed *ParsedExpression Ident *string }
StoredExpressionPart is a lightweight expression part which only stores a hash of the predicate to reduce memory usage.
type StringExpression ¶
type StringExpression string
StringExpression is a string type that implements Evaluable, useful for basic ephemeral expressions that have no lifetime.
func (StringExpression) GetExpression ¶
func (s StringExpression) GetExpression() string
func (StringExpression) GetID ¶
func (s StringExpression) GetID() uuid.UUID
type TreeParser ¶
type TreeParser interface {
Parse(ctx context.Context, eval Evaluable) (*ParsedExpression, error)
}
TreeParser parses an expression into a tree, with a root node and branches for each subsequent OR or AND expression.
func NewTreeParser ¶
func NewTreeParser(ep CELCompiler) TreeParser
NewTreeParser returns a new tree parser for a given *cel.Env