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

162 lines
5.4 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) {
// Selection defines which rows are evaluated. Selected rows have a true value
// in the selection vector. When selection has Len() == 0, all rows are selected.
// When selection has Len() > 0, only rows with a true bit are selected. Rows that
// are not selected are treated as null in array results.
//
// We hide selection vector from the caller because we don't support selection on expressions that are columns.
// E.g., evaluateWithSelection(alloc, &expr.Column{...}, batch, onlyFirstAndThirdRows) will return all the rows.
// This is not a problem though as selection vector can be not "allSelected" only when the expression contains a
// BinOp that can be short-circuited.
allSelected := memory.Bitmap{}
return evaluateWithSelection(alloc, expr, batch, allSelected)
}
func evaluateWithSelection(alloc *memory.Allocator, expr Expression, batch *columnar.RecordBatch, selection memory.Bitmap) (columnar.Datum, error) {
nrows := 0
if batch != nil {
nrows = int(batch.NumRows())
}
if selection.Len() > 0 && selection.Len() != nrows {
return nil, fmt.Errorf("selection length mismatch: %d != %d", selection.Len(), nrows)
}
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, selection)
case *Binary:
return evaluateBinary(alloc, expr, batch, selection)
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, selection memory.Bitmap) (columnar.Datum, error) {
switch expr.Op {
case UnaryOpNOT:
value, err := evaluateWithSelection(alloc, expr.Value, batch, selection)
if err != nil {
return nil, err
}
return compute.Not(alloc, value, selection)
default:
return nil, fmt.Errorf("unexpected unary operator %s", expr.Op)
}
}
func evaluateBinary(alloc *memory.Allocator, expr *Binary, batch *columnar.RecordBatch, selection memory.Bitmap) (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, selection)
}
// TODO(rfratto): If expr.Op is [BinaryOpAND] or [BinaryOpOR], we can
// propagate selection vectors to avoid unnecessary evaluations.
left, err := evaluateWithSelection(alloc, expr.Left, batch, selection)
if err != nil {
return nil, err
}
right, err := evaluateWithSelection(alloc, expr.Right, batch, selection)
if err != nil {
return nil, err
}
switch expr.Op {
case BinaryOpEQ:
return compute.Equals(alloc, left, right, selection)
case BinaryOpNEQ:
return compute.NotEquals(alloc, left, right, selection)
case BinaryOpGT:
return compute.GreaterThan(alloc, left, right, selection)
case BinaryOpGTE:
return compute.GreaterOrEqual(alloc, left, right, selection)
case BinaryOpLT:
return compute.LessThan(alloc, left, right, selection)
case BinaryOpLTE:
return compute.LessOrEqual(alloc, left, right, selection)
case BinaryOpAND:
return compute.And(alloc, left, right, selection)
case BinaryOpOR:
return compute.Or(alloc, left, right, selection)
case BinaryOpHasSubstr:
return compute.Substr(alloc, left, right, selection)
case BinaryOpHasSubstrIgnoreCase:
return compute.SubstrInsensitive(alloc, left, right, selection)
default:
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, selection memory.Bitmap) (columnar.Datum, error) {
switch expr.Op {
case BinaryOpMatchRegex:
left, err := evaluateWithSelection(alloc, expr.Left, batch, selection)
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, selection)
case BinaryOpIn:
left, err := evaluateWithSelection(alloc, expr.Left, batch, selection)
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, selection)
}
return nil, fmt.Errorf("unexpected binary operator %s", expr.Op)
}