mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-20 15:16:42 +00:00
wip
This commit is contained in:
parent
dc245f82f3
commit
dfcb8dfd65
3 changed files with 676 additions and 0 deletions
|
@ -148,6 +148,12 @@ func parsePipe(lex *lexer) (pipe, error) {
|
||||||
return nil, fmt.Errorf("cannot parse 'limit' pipe: %w", err)
|
return nil, fmt.Errorf("cannot parse 'limit' pipe: %w", err)
|
||||||
}
|
}
|
||||||
return pl, nil
|
return pl, nil
|
||||||
|
case lex.isKeyword("math"):
|
||||||
|
pm, err := parsePipeMath(lex, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse 'math' pipe: %w", err)
|
||||||
|
}
|
||||||
|
return pm, nil
|
||||||
case lex.isKeyword("offset", "skip"):
|
case lex.isKeyword("offset", "skip"):
|
||||||
ps, err := parsePipeOffset(lex)
|
ps, err := parsePipeOffset(lex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -224,6 +230,13 @@ func parsePipe(lex *lexer) (pipe, error) {
|
||||||
}
|
}
|
||||||
lex.restoreState(lexState)
|
lex.restoreState(lexState)
|
||||||
|
|
||||||
|
// Try parsing math pipe without 'math' keyword
|
||||||
|
pm, err := parsePipeMath(lex, false)
|
||||||
|
if err == nil {
|
||||||
|
return pm, nil
|
||||||
|
}
|
||||||
|
lex.restoreState(lexState)
|
||||||
|
|
||||||
// Try parsing filter pipe without 'filter' keyword
|
// Try parsing filter pipe without 'filter' keyword
|
||||||
pf, err := parsePipeFilter(lex, false)
|
pf, err := parsePipeFilter(lex, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
500
lib/logstorage/pipe_math.go
Normal file
500
lib/logstorage/pipe_math.go
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
package logstorage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pipeMath processes '| math ...' pipe.
|
||||||
|
//
|
||||||
|
// See https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe
|
||||||
|
type pipeMath struct {
|
||||||
|
entries []*mathEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type mathEntry struct {
|
||||||
|
resultField string
|
||||||
|
be *mathBinaryExpr
|
||||||
|
neededFields []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pipeMath) String() string {
|
||||||
|
s := "math"
|
||||||
|
a := make([]string, len(pm.entries))
|
||||||
|
for i, e := range pm.entries {
|
||||||
|
a[i] = e.String()
|
||||||
|
}
|
||||||
|
s += " " + strings.Join(a, ", ")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (me *mathEntry) String() string {
|
||||||
|
return quoteTokenIfNeeded(me.resultField) + " = " + me.be.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pipeMath) updateNeededFields(neededFields, unneededFields fieldsSet) {
|
||||||
|
for i := len(pm.entries) - 1; i >= 0; i-- {
|
||||||
|
e := pm.entries[i]
|
||||||
|
if neededFields.contains("*") {
|
||||||
|
if !unneededFields.contains(e.resultField) {
|
||||||
|
unneededFields.add(e.resultField)
|
||||||
|
unneededFields.removeFields(e.neededFields)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if neededFields.contains(e.resultField) {
|
||||||
|
neededFields.remove(e.resultField)
|
||||||
|
neededFields.addFields(e.neededFields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pipeMath) optimize() {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pipeMath) hasFilterInWithQuery() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pipeMath) initFilterInValues(_ map[string][]string, _ getFieldValuesFunc) (pipe, error) {
|
||||||
|
return pm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pipeMath) newPipeProcessor(workersCount int, _ <-chan struct{}, _ func(), ppNext pipeProcessor) pipeProcessor {
|
||||||
|
pmp := &pipeMathProcessor{
|
||||||
|
pm: pm,
|
||||||
|
ppNext: ppNext,
|
||||||
|
|
||||||
|
shards: make([]pipeMathProcessorShard, workersCount),
|
||||||
|
}
|
||||||
|
return pmp
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeMathProcessor struct {
|
||||||
|
pm *pipeMath
|
||||||
|
ppNext pipeProcessor
|
||||||
|
|
||||||
|
shards []pipeMathProcessorShard
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeMathProcessorShard struct {
|
||||||
|
pipeMathProcessorShardNopad
|
||||||
|
|
||||||
|
// The padding prevents false sharing on widespread platforms with 128 mod (cache line size) = 0 .
|
||||||
|
_ [128 - unsafe.Sizeof(pipeMathProcessorShardNopad{})%128]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeMathProcessorShardNopad struct {
|
||||||
|
a arena
|
||||||
|
rcs []resultColumn
|
||||||
|
|
||||||
|
rs [][]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *pipeMathProcessorShard) executeMathEntry(e *mathEntry, rc *resultColumn, br *blockResult) {
|
||||||
|
shard.rs = shard.rs[:0]
|
||||||
|
shard.executeBinaryExpr(e.be, br)
|
||||||
|
r := shard.rs[0]
|
||||||
|
|
||||||
|
b := shard.a.b
|
||||||
|
for _, f := range r {
|
||||||
|
bLen := len(b)
|
||||||
|
b = marshalFloat64String(b, f)
|
||||||
|
v := bytesutil.ToUnsafeString(b[bLen:])
|
||||||
|
rc.addValue(v)
|
||||||
|
}
|
||||||
|
shard.a.b = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *pipeMathProcessorShard) executeBinaryExpr(expr *mathBinaryExpr, br *blockResult) {
|
||||||
|
rIdx := len(shard.rs)
|
||||||
|
shard.rs = slicesutil.SetLength(shard.rs, len(shard.rs)+1)
|
||||||
|
shard.rs[rIdx] = slicesutil.SetLength(shard.rs[rIdx], len(br.timestamps))
|
||||||
|
|
||||||
|
if expr.isConst {
|
||||||
|
r := shard.rs[rIdx]
|
||||||
|
for i := range br.timestamps {
|
||||||
|
r[i] = expr.constValue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expr.fieldName != "" {
|
||||||
|
c := br.getColumnByName(expr.fieldName)
|
||||||
|
values := c.getValues(br)
|
||||||
|
r := shard.rs[rIdx]
|
||||||
|
var f float64
|
||||||
|
for i, v := range values {
|
||||||
|
if i == 0 || v != values[i-1] {
|
||||||
|
var ok bool
|
||||||
|
f, ok = tryParseFloat64(v)
|
||||||
|
if !ok {
|
||||||
|
f = nan
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r[i] = f
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shard.executeBinaryExpr(expr.left, br)
|
||||||
|
shard.executeBinaryExpr(expr.right, br)
|
||||||
|
|
||||||
|
r := shard.rs[rIdx]
|
||||||
|
rLeft := shard.rs[rIdx+1]
|
||||||
|
rRight := shard.rs[rIdx+2]
|
||||||
|
|
||||||
|
mathFunc := expr.mathFunc
|
||||||
|
for i := range r {
|
||||||
|
r[i] = mathFunc(rLeft[i], rRight[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
shard.rs = shard.rs[:rIdx+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pmp *pipeMathProcessor) writeBlock(workerID uint, br *blockResult) {
|
||||||
|
if len(br.timestamps) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shard := &pmp.shards[workerID]
|
||||||
|
entries := pmp.pm.entries
|
||||||
|
|
||||||
|
shard.rcs = slicesutil.SetLength(shard.rcs, len(entries))
|
||||||
|
rcs := shard.rcs
|
||||||
|
for i, e := range entries {
|
||||||
|
rc := &rcs[i]
|
||||||
|
rc.name = e.resultField
|
||||||
|
shard.executeMathEntry(e, rc, br)
|
||||||
|
br.addResultColumn(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pmp.ppNext.writeBlock(workerID, br)
|
||||||
|
|
||||||
|
for i := range rcs {
|
||||||
|
rcs[i].resetValues()
|
||||||
|
}
|
||||||
|
shard.a.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pmp *pipeMathProcessor) flush() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePipeMath(lex *lexer, needMathKeyword bool) (*pipeMath, error) {
|
||||||
|
if needMathKeyword {
|
||||||
|
if !lex.isKeyword("math") {
|
||||||
|
return nil, fmt.Errorf("unexpected token: %q; want %q", lex.token, "math")
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
var mes []*mathEntry
|
||||||
|
for {
|
||||||
|
me, err := parseMathEntry(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mes = append(mes, me)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case lex.isKeyword(","):
|
||||||
|
lex.nextToken()
|
||||||
|
case lex.isKeyword("|", ")", ""):
|
||||||
|
if len(mes) == 0 {
|
||||||
|
return nil, fmt.Errorf("missing 'math' expressions")
|
||||||
|
}
|
||||||
|
pm := &pipeMath{
|
||||||
|
entries: mes,
|
||||||
|
}
|
||||||
|
return pm, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected token after 'math' expression [%s]: %q; expacting ',', '|' or ')'", mes[len(mes)-1], lex.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMathEntry(lex *lexer) (*mathEntry, error) {
|
||||||
|
resultField, err := parseFieldName(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse result name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lex.isKeyword("=") {
|
||||||
|
return nil, fmt.Errorf("missing '=' after %q", resultField)
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
|
|
||||||
|
be, err := parseMathBinaryExpr(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse expression after '%q=': %w", resultField, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
neededFields := newFieldsSet()
|
||||||
|
be.updateNeededFields(neededFields)
|
||||||
|
|
||||||
|
me := &mathEntry{
|
||||||
|
resultField: resultField,
|
||||||
|
be: be,
|
||||||
|
neededFields: neededFields.getAll(),
|
||||||
|
}
|
||||||
|
return me, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mathBinaryExpr struct {
|
||||||
|
isConst bool
|
||||||
|
constValue float64
|
||||||
|
constValueStr string
|
||||||
|
|
||||||
|
fieldName string
|
||||||
|
|
||||||
|
left *mathBinaryExpr
|
||||||
|
right *mathBinaryExpr
|
||||||
|
op string
|
||||||
|
|
||||||
|
mathFunc func(a, b float64) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *mathBinaryExpr) String() string {
|
||||||
|
if be.isConst {
|
||||||
|
return be.constValueStr
|
||||||
|
}
|
||||||
|
if be.fieldName != "" {
|
||||||
|
return quoteTokenIfNeeded(be.fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
leftStr := be.left.String()
|
||||||
|
rightStr := be.right.String()
|
||||||
|
|
||||||
|
if binaryOpPriority(be.op) > binaryOpPriority(be.left.op) {
|
||||||
|
leftStr = "(" + leftStr + ")"
|
||||||
|
}
|
||||||
|
if binaryOpPriority(be.op) > binaryOpPriority(be.right.op) {
|
||||||
|
rightStr = "(" + rightStr + ")"
|
||||||
|
}
|
||||||
|
if be.op == "unary_minus" {
|
||||||
|
// Unary minus
|
||||||
|
return "-" + rightStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return leftStr + " " + be.op + " " + rightStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *mathBinaryExpr) updateNeededFields(neededFields fieldsSet) {
|
||||||
|
if be.isConst {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if be.fieldName != "" {
|
||||||
|
neededFields.add(be.fieldName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
be.left.updateNeededFields(neededFields)
|
||||||
|
be.right.updateNeededFields(neededFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMathBinaryExpr(lex *lexer) (*mathBinaryExpr, error) {
|
||||||
|
// parse left operand
|
||||||
|
leftParens := lex.isKeyword("(")
|
||||||
|
left, err := parseMathBinaryExprOperand(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lex.isKeyword("|", ")", ",", "") {
|
||||||
|
// There is no right operand
|
||||||
|
return left, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
again:
|
||||||
|
// parse operator
|
||||||
|
op := lex.token
|
||||||
|
lex.nextToken()
|
||||||
|
|
||||||
|
mathFunc, err := getMathFuncForOp(op)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse right operand
|
||||||
|
right, err := parseMathBinaryExprOperand(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse right operand after [%s %s]: %w", left, op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
be := &mathBinaryExpr{
|
||||||
|
left: left,
|
||||||
|
right: right,
|
||||||
|
op: op,
|
||||||
|
mathFunc: mathFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
// balance operands according to their priority
|
||||||
|
if !leftParens && binaryOpPriority(op) > binaryOpPriority(left.op) {
|
||||||
|
be.left = left.right
|
||||||
|
left.right = be
|
||||||
|
be = left
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lex.isKeyword("|", ")", ",", "") {
|
||||||
|
left = be
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMathFuncForOp(op string) (func(a, b float64) float64, error) {
|
||||||
|
switch op {
|
||||||
|
case "+":
|
||||||
|
return mathFuncPlus, nil
|
||||||
|
case "-":
|
||||||
|
return mathFuncMinus, nil
|
||||||
|
case "*":
|
||||||
|
return mathFuncMul, nil
|
||||||
|
case "/":
|
||||||
|
return mathFuncDiv, nil
|
||||||
|
case "%":
|
||||||
|
return mathFuncMod, nil
|
||||||
|
case "^":
|
||||||
|
return mathFuncPow, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported math operator: %q; supported operators: '+', '-', '*', '/'", op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathFuncPlus(a, b float64) float64 {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathFuncMinus(a, b float64) float64 {
|
||||||
|
return a - b
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathFuncMul(a, b float64) float64 {
|
||||||
|
return a * b
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathFuncDiv(a, b float64) float64 {
|
||||||
|
return a / b
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathFuncMod(a, b float64) float64 {
|
||||||
|
return math.Mod(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathFuncPow(a, b float64) float64 {
|
||||||
|
return math.Pow(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func binaryOpPriority(op string) int {
|
||||||
|
switch op {
|
||||||
|
case "+", "-":
|
||||||
|
return 10
|
||||||
|
case "*", "/", "%":
|
||||||
|
return 20
|
||||||
|
case "^":
|
||||||
|
return 30
|
||||||
|
case "unary_minus":
|
||||||
|
return 40
|
||||||
|
case "":
|
||||||
|
return 100
|
||||||
|
default:
|
||||||
|
logger.Panicf("BUG: unexpected binary operation %q", op)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMathBinaryExprInParens(lex *lexer) (*mathBinaryExpr, error) {
|
||||||
|
if !lex.isKeyword("(") {
|
||||||
|
return nil, fmt.Errorf("missing '('")
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
|
|
||||||
|
be, err := parseMathBinaryExpr(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lex.isKeyword(")") {
|
||||||
|
return nil, fmt.Errorf("missing ')'; got %q instead", lex.token)
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMathBinaryExprOperand(lex *lexer) (*mathBinaryExpr, error) {
|
||||||
|
if lex.isKeyword("(") {
|
||||||
|
return parseMathBinaryExprInParens(lex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lex.isKeyword("-") {
|
||||||
|
lex.nextToken()
|
||||||
|
be, err := parseMathBinaryExprOperand(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
be = &mathBinaryExpr{
|
||||||
|
left: &mathBinaryExpr{
|
||||||
|
isConst: true,
|
||||||
|
},
|
||||||
|
right: be,
|
||||||
|
op: "unary_minus",
|
||||||
|
mathFunc: mathFuncMinus,
|
||||||
|
}
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if lex.isNumber() {
|
||||||
|
numStr, err := getCompoundMathToken(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse number: %w", err)
|
||||||
|
}
|
||||||
|
f, ok := tryParseNumber(numStr)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot parse number from %q", numStr)
|
||||||
|
}
|
||||||
|
be := &mathBinaryExpr{
|
||||||
|
isConst: true,
|
||||||
|
constValue: f,
|
||||||
|
constValueStr: numStr,
|
||||||
|
}
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName, err := getCompoundMathToken(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fieldName = getCanonicalColumnName(fieldName)
|
||||||
|
be := &mathBinaryExpr{
|
||||||
|
fieldName: fieldName,
|
||||||
|
}
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCompoundMathToken(lex *lexer) (string, error) {
|
||||||
|
stopTokens := []string{"+", "*", "/", "%", "^", ",", ")", "|", ""}
|
||||||
|
if lex.isKeyword(stopTokens...) {
|
||||||
|
return "", fmt.Errorf("compound token cannot start with '%s'", lex.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := lex.token
|
||||||
|
rawS := lex.rawToken
|
||||||
|
lex.nextToken()
|
||||||
|
suffix := ""
|
||||||
|
stopTokens = append(stopTokens, "-")
|
||||||
|
for !lex.isSkippedSpace && !lex.isKeyword(stopTokens...) {
|
||||||
|
s += lex.token
|
||||||
|
lex.nextToken()
|
||||||
|
}
|
||||||
|
if suffix == "" {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
return rawS + suffix, nil
|
||||||
|
}
|
163
lib/logstorage/pipe_math_test.go
Normal file
163
lib/logstorage/pipe_math_test.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
package logstorage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParsePipeMathSuccess(t *testing.T) {
|
||||||
|
f := func(pipeStr string) {
|
||||||
|
t.Helper()
|
||||||
|
expectParsePipeSuccess(t, pipeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
f(`math a = b`)
|
||||||
|
f(`math a = -123`)
|
||||||
|
f(`math a = 12.345KB`)
|
||||||
|
f(`math a = -2 + 2`)
|
||||||
|
f(`math a = x, y = z`)
|
||||||
|
f(`math a = foo / bar + baz * abc % -45ms`)
|
||||||
|
f(`math a = foo / (bar + baz) * abc ^ 2`)
|
||||||
|
f(`math a = foo / ((bar + baz) * abc) ^ -2`)
|
||||||
|
f(`math a = foo + bar / baz - abc`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePipeMathFailure(t *testing.T) {
|
||||||
|
f := func(pipeStr string) {
|
||||||
|
t.Helper()
|
||||||
|
expectParsePipeFailure(t, pipeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
f(`math`)
|
||||||
|
f(`math x`)
|
||||||
|
f(`math x y`)
|
||||||
|
f(`math x =`)
|
||||||
|
f(`math x = (`)
|
||||||
|
f(`math x = a +`)
|
||||||
|
f(`math x = a + (`)
|
||||||
|
f(`math x = a + )`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPipeMath(t *testing.T) {
|
||||||
|
f := func(pipeStr string, rows, rowsExpected [][]Field) {
|
||||||
|
t.Helper()
|
||||||
|
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
f("math a = 1", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("math a = 10 * 5 - 3", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "47"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("math a = -1.5K", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "-1500"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("math a = b", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "2"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("math a = a", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "NaN"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("math x = 2*c + b", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v1"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
{"x", "8"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("math a = (2*c + (b%c))/(c-b)^(b-1)", [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "v"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "x"},
|
||||||
|
{"b", "3"},
|
||||||
|
{"c", "5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"b", "3"},
|
||||||
|
{"c", "6"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "8"},
|
||||||
|
{"b", "2"},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "42.25"},
|
||||||
|
{"b", "3"},
|
||||||
|
{"c", "5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "25"},
|
||||||
|
{"b", "3"},
|
||||||
|
{"c", "6"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue