Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/expr/evaluate.go

138 lines
3.9 KiB

package expr
import (
"fmt"
"github.com/grafana/loki/v3/pkg/columnar"
"github.com/grafana/loki/v3/pkg/compute"
"github.com/grafana/loki/v3/pkg/memory"
)
// Evaluate processes expr against the provided batch, producing a datum as a
// result using alloc.
//
// The return type of Evaluate depends on the expression provided. See the
// documentation for implementations of Expression for what they produce when
// evaluated.
func Evaluate(alloc *memory.Allocator, expr Expression, batch *columnar.RecordBatch) (columnar.Datum, error) {
switch expr := expr.(type) {
case *Constant:
return expr.Value, nil
case *Column:
columnIndex := -1
if schema := batch.Schema(); schema != nil {
_, columnIndex = schema.ColumnIndex(expr.Name)
}
if columnIndex == -1 {
validity := memory.NewBitmap(alloc, int(batch.NumRows()))
validity.AppendCount(false, int(batch.NumRows()))
return columnar.NewNull(validity), nil
}
return batch.Column(int64(columnIndex)), nil
case *Unary:
return evaluateUnary(alloc, expr, batch)
case *Binary:
return evaluateBinary(alloc, expr, batch)
case *Regexp:
return nil, fmt.Errorf("regexp can only be evaluated as the right-hand side of regex match operations")
default:
panic(fmt.Sprintf("unexpected expression type %T", expr))
}
}
func evaluateUnary(alloc *memory.Allocator, expr *Unary, batch *columnar.RecordBatch) (columnar.Datum, error) {
switch expr.Op {
case UnaryOpNOT:
value, err := Evaluate(alloc, expr.Value, batch)
if err != nil {
return nil, err
}
return compute.Not(alloc, value)
}
return nil, fmt.Errorf("unexpected unary operator %s", expr.Op)
}
func evaluateBinary(alloc *memory.Allocator, expr *Binary, batch *columnar.RecordBatch) (columnar.Datum, error) {
// Check for special operators that need different handling of their arguments.
switch expr.Op {
case BinaryOpMatchRegex, BinaryOpIn:
return evaluateSpecialBinary(alloc, expr, batch)
}
// TODO(rfratto): If expr.Op is [BinaryOpAND] or [BinaryOpOR], we can
// propagate selection vectors to avoid unnecessary evaluations.
left, err := Evaluate(alloc, expr.Left, batch)
if err != nil {
return nil, err
}
right, err := Evaluate(alloc, expr.Right, batch)
if err != nil {
return nil, err
}
switch expr.Op {
case BinaryOpEQ:
return compute.Equals(alloc, left, right)
case BinaryOpNEQ:
return compute.NotEquals(alloc, left, right)
case BinaryOpGT:
return compute.GreaterThan(alloc, left, right)
case BinaryOpGTE:
return compute.GreaterOrEqual(alloc, left, right)
case BinaryOpLT:
return compute.LessThan(alloc, left, right)
case BinaryOpLTE:
return compute.LessOrEqual(alloc, left, right)
case BinaryOpAND:
return compute.And(alloc, left, right)
case BinaryOpOR:
return compute.Or(alloc, left, right)
case BinaryOpHasSubstr:
return compute.Substr(alloc, left, right)
case BinaryOpHasSubstrIgnoreCase:
return compute.SubstrInsensitive(alloc, left, right)
}
return nil, fmt.Errorf("unexpected binary operator %s", expr.Op)
}
// evaluateSpecialBinary evaluates binary expressions for which one of the
// arguments does not evaluate into an expression of its own.
func evaluateSpecialBinary(alloc *memory.Allocator, expr *Binary, batch *columnar.RecordBatch) (columnar.Datum, error) {
switch expr.Op {
case BinaryOpMatchRegex:
left, err := Evaluate(alloc, expr.Left, batch)
if err != nil {
return nil, err
}
right, ok := expr.Right.(*Regexp)
if !ok {
return nil, fmt.Errorf("right-hand side of regex match operation must be a regexp, got %T", expr.Right)
}
return compute.RegexpMatch(alloc, left, right.Expression)
case BinaryOpIn:
left, err := Evaluate(alloc, expr.Left, batch)
if err != nil {
return nil, err
}
right, ok := expr.Right.(*ValueSet)
if !ok {
return nil, fmt.Errorf("right-hand side of in operation must be a ValueSet, got %T", expr.Right)
}
return compute.IsMember(alloc, left, right.Values)
}
return nil, fmt.Errorf("unexpected binary operator %s", expr.Op)
}