This commit is contained in:
Aliaksandr Valialkin 2024-05-17 17:15:18 +02:00
parent 01fc253eb7
commit d95b491809
No known key found for this signature in database
GPG key ID: 52C003EE2BCDB9EB
3 changed files with 100 additions and 8 deletions

View file

@ -11,6 +11,7 @@ import (
// Example LogsQL: `fieldName:range(minValue, maxValue]` // Example LogsQL: `fieldName:range(minValue, maxValue]`
type filterRange struct { type filterRange struct {
fieldName string fieldName string
minValue float64 minValue float64
maxValue float64 maxValue float64
@ -18,7 +19,7 @@ type filterRange struct {
} }
func (fr *filterRange) String() string { func (fr *filterRange) String() string {
return quoteFieldNameIfNeeded(fr.fieldName) + "range" + fr.stringRepr return quoteFieldNameIfNeeded(fr.fieldName) + fr.stringRepr
} }
func (fr *filterRange) updateNeededFields(neededFields fieldsSet) { func (fr *filterRange) updateNeededFields(neededFields fieldsSet) {

View file

@ -406,6 +406,10 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) {
return nil, fmt.Errorf("missing whitespace before the search word %q", lex.prevToken) return nil, fmt.Errorf("missing whitespace before the search word %q", lex.prevToken)
} }
return parseParensFilter(lex, fieldName) return parseParensFilter(lex, fieldName)
case lex.isKeyword(">"):
return parseFilterGT(lex, fieldName)
case lex.isKeyword("<"):
return parseFilterLT(lex, fieldName)
case lex.isKeyword("not", "!"): case lex.isKeyword("not", "!"):
return parseFilterNot(lex, fieldName) return parseFilterNot(lex, fieldName)
case lex.isKeyword("exact"): case lex.isKeyword("exact"):
@ -754,6 +758,70 @@ func parseFilterRegexp(lex *lexer, fieldName string) (filter, error) {
}) })
} }
func parseFilterGT(lex *lexer, fieldName string) (filter, error) {
if fieldName == "" {
return nil, fmt.Errorf("'>' and '>=' must be prefixed with the field name")
}
lex.nextToken()
includeMinValue := false
op := ">"
if lex.isKeyword("=") {
lex.nextToken()
includeMinValue = true
op = ">="
}
minValue, fStr, err := parseFloat64(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse number after '%s': %w", op, err)
}
if !includeMinValue {
minValue = nextafter(minValue, inf)
}
fr := &filterRange{
fieldName: fieldName,
minValue: minValue,
maxValue: inf,
stringRepr: op + fStr,
}
return fr, nil
}
func parseFilterLT(lex *lexer, fieldName string) (filter, error) {
if fieldName == "" {
return nil, fmt.Errorf("'<' and '<=' must be prefixed with the field name")
}
lex.nextToken()
includeMaxValue := false
op := "<"
if lex.isKeyword("=") {
lex.nextToken()
includeMaxValue = true
op = "<="
}
maxValue, fStr, err := parseFloat64(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse number after '%s': %w", op, err)
}
if !includeMaxValue {
maxValue = nextafter(maxValue, -inf)
}
fr := &filterRange{
fieldName: fieldName,
minValue: -inf,
maxValue: maxValue,
stringRepr: op + fStr,
}
return fr, nil
}
func parseFilterRange(lex *lexer, fieldName string) (filter, error) { func parseFilterRange(lex *lexer, fieldName string) (filter, error) {
funcName := lex.token funcName := lex.token
lex.nextToken() lex.nextToken()
@ -801,19 +869,19 @@ func parseFilterRange(lex *lexer, fieldName string) (filter, error) {
} }
lex.nextToken() lex.nextToken()
stringRepr := "" stringRepr := "range"
if includeMinValue { if includeMinValue {
stringRepr += "[" stringRepr += "["
} else { } else {
stringRepr += "(" stringRepr += "("
minValue = math.Nextafter(minValue, inf) minValue = nextafter(minValue, inf)
} }
stringRepr += minValueStr + ", " + maxValueStr stringRepr += minValueStr + ", " + maxValueStr
if includeMaxValue { if includeMaxValue {
stringRepr += "]" stringRepr += "]"
} else { } else {
stringRepr += ")" stringRepr += ")"
maxValue = math.Nextafter(maxValue, -inf) maxValue = nextafter(maxValue, -inf)
} }
fr := &filterRange{ fr := &filterRange{
@ -1216,3 +1284,10 @@ func parseInt(s string) (int64, error) {
} }
return nn, nil return nn, nil
} }
func nextafter(f, xInf float64) float64 {
if math.IsInf(f, 0) {
return f
}
return math.Nextafter(f, xInf)
}

View file

@ -1,7 +1,6 @@
package logstorage package logstorage
import ( import (
"math"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -492,15 +491,25 @@ func TestParseRangeFilter(t *testing.T) {
f(`range:range["-1.234e5", "-2e-5"]`, `range`, -1.234e5, -2e-5) f(`range:range["-1.234e5", "-2e-5"]`, `range`, -1.234e5, -2e-5)
f(`_msg:range[1, 2]`, `_msg`, 1, 2) f(`_msg:range[1, 2]`, `_msg`, 1, 2)
f(`:range(1, 2)`, ``, math.Nextafter(1, inf), math.Nextafter(2, -inf)) f(`:range(1, 2)`, ``, nextafter(1, inf), nextafter(2, -inf))
f(`range[1, 2)`, ``, 1, math.Nextafter(2, -inf)) f(`range[1, 2)`, ``, 1, nextafter(2, -inf))
f(`range("1", 2]`, ``, math.Nextafter(1, inf), 2) f(`range("1", 2]`, ``, nextafter(1, inf), 2)
f(`response_size:range[1KB, 10MiB]`, `response_size`, 1_000, 10*(1<<20)) f(`response_size:range[1KB, 10MiB]`, `response_size`, 1_000, 10*(1<<20))
f(`response_size:range[1G, 10Ti]`, `response_size`, 1_000_000_000, 10*(1<<40)) f(`response_size:range[1G, 10Ti]`, `response_size`, 1_000_000_000, 10*(1<<40))
f(`response_size:range[10, inf]`, `response_size`, 10, inf) f(`response_size:range[10, inf]`, `response_size`, 10, inf)
f(`duration:range[100ns, 1y2w2.5m3s5ms]`, `duration`, 100, 1*nsecsPerYear+2*nsecsPerWeek+2.5*nsecsPerMinute+3*nsecsPerSecond+5*nsecsPerMillisecond) f(`duration:range[100ns, 1y2w2.5m3s5ms]`, `duration`, 100, 1*nsecsPerYear+2*nsecsPerWeek+2.5*nsecsPerMinute+3*nsecsPerSecond+5*nsecsPerMillisecond)
f(`foo:>10.43`, `foo`, nextafter(10.43, inf), inf)
f(`foo: > -10.43`, `foo`, nextafter(-10.43, inf), inf)
f(`foo:>=10.43`, `foo`, 10.43, inf)
f(`foo: >= -10.43`, `foo`, -10.43, inf)
f(`foo:<10.43`, `foo`, -inf, nextafter(10.43, -inf))
f(`foo: < -10.43`, `foo`, -inf, nextafter(-10.43, -inf))
f(`foo:<=10.43`, `foo`, -inf, 10.43)
f(`foo: <= 10.43`, `foo`, -inf, 10.43)
} }
func TestParseQuerySuccess(t *testing.T) { func TestParseQuerySuccess(t *testing.T) {
@ -723,6 +732,13 @@ func TestParseQuerySuccess(t *testing.T) {
f(`range(0x1ff, inf)`, `range(0x1ff, inf)`) f(`range(0x1ff, inf)`, `range(0x1ff, inf)`)
f(`range(-INF,+inF)`, `range(-INF, +inF)`) f(`range(-INF,+inF)`, `range(-INF, +inF)`)
f(`range(1.5K, 22.5GiB)`, `range(1.5K, 22.5GiB)`) f(`range(1.5K, 22.5GiB)`, `range(1.5K, 22.5GiB)`)
f(`foo:range(5,inf)`, `foo:range(5, inf)`)
// >, >=, < and <= filter
f(`foo: > 10.5M`, `foo:>10.5M`)
f(`foo: >= 10.5M`, `foo:>=10.5M`)
f(`foo: < 10.5M`, `foo:<10.5M`)
f(`foo: <= 10.5M`, `foo:<=10.5M`)
// re filter // re filter
f("re('foo|ba(r.+)')", `re("foo|ba(r.+)")`) f("re('foo|ba(r.+)')", `re("foo|ba(r.+)")`)