diff --git a/llx/builtin.go b/llx/builtin.go index c981d6f48d..1b5ac846c6 100644 --- a/llx/builtin.go +++ b/llx/builtin.go @@ -176,6 +176,7 @@ func init() { string("||" + types.ArrayLike): {f: intOrArrayV2, Label: "||"}, string("&&" + types.MapLike): {f: intAndMapV2, Label: "&&"}, string("||" + types.MapLike): {f: intOrMapV2, Label: "||"}, + string("inRange"): {f: intInRange, Label: "inRange"}, }, types.Float: { // == / != @@ -246,6 +247,7 @@ func init() { string("||" + types.ArrayLike): {f: floatOrArrayV2, Label: "||"}, string("&&" + types.MapLike): {f: floatAndMapV2, Label: "&&"}, string("||" + types.MapLike): {f: floatOrMapV2, Label: "&&"}, + string("inRange"): {f: floatInRange, Label: "inRange"}, }, types.String: { // == / != diff --git a/llx/builtin_simple.go b/llx/builtin_simple.go index 38b6cb59ce..fe98e685f3 100644 --- a/llx/builtin_simple.go +++ b/llx/builtin_simple.go @@ -1568,6 +1568,92 @@ func mapOrIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*Raw return boolOpV2(e, bind, chunk, ref, opMapOrInt) } +func int64InRange(e *blockExecutor, val int64, chunk *Chunk, ref uint64) (*RawData, uint64, error) { + minRef := chunk.Function.Args[0] + min, rref, err := e.resolveValue(minRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + switch minval := min.Value.(type) { + case int64: + if val < minval { + return BoolFalse, 0, nil + } + case float64: + if float64(val) < minval { + return BoolFalse, 0, nil + } + } + + maxRef := chunk.Function.Args[1] + max, rref, err := e.resolveValue(maxRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + switch maxval := max.Value.(type) { + case int64: + if val > maxval { + return BoolFalse, 0, nil + } + case float64: + if float64(val) > maxval { + return BoolFalse, 0, nil + } + } + + return BoolTrue, 0, nil +} + +func float64InRange(e *blockExecutor, val float64, chunk *Chunk, ref uint64) (*RawData, uint64, error) { + minRef := chunk.Function.Args[0] + min, rref, err := e.resolveValue(minRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + switch minval := min.Value.(type) { + case int64: + if val < float64(minval) { + return BoolFalse, 0, nil + } + case float64: + if val < minval { + return BoolFalse, 0, nil + } + } + + maxRef := chunk.Function.Args[1] + max, rref, err := e.resolveValue(maxRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + switch maxval := max.Value.(type) { + case int64: + if val > float64(maxval) { + return BoolFalse, 0, nil + } + case float64: + if val > maxval { + return BoolFalse, 0, nil + } + } + + return BoolTrue, 0, nil +} + +func intInRange(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { + val := bind.Value.(int64) + return int64InRange(e, val, chunk, ref) +} + +func floatInRange(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { + val := bind.Value.(float64) + return float64InRange(e, val, chunk, ref) +} + // float &&/|| T func opFloatAndString(left interface{}, right interface{}) bool { diff --git a/mqlc/builtin.go b/mqlc/builtin.go index c2a5274649..a8599019b9 100644 --- a/mqlc/builtin.go +++ b/mqlc/builtin.go @@ -34,6 +34,12 @@ var builtinFunctions map[types.Type]map[string]compileHandler func init() { builtinFunctions = map[types.Type]map[string]compileHandler{ + types.Int: { + "inRange": {typ: boolType, compile: compileInRange}, + }, + types.Float: { + "inRange": {typ: boolType, compile: compileInRange}, + }, types.String: { "contains": {compile: compileStringContains, typ: boolType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}}, "find": {typ: stringArrayType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Regex}}}, diff --git a/mqlc/builtin_simple.go b/mqlc/builtin_simple.go index 9ce5176fc0..9d490ffb0e 100644 --- a/mqlc/builtin_simple.go +++ b/mqlc/builtin_simple.go @@ -5,6 +5,7 @@ package mqlc import ( "errors" + "strconv" "go.mondoo.com/cnquery/v9/llx" "go.mondoo.com/cnquery/v9/mqlc/parser" @@ -102,3 +103,62 @@ func compileStringContains(c *compiler, typ types.Type, ref uint64, id string, c return types.Nil, errors.New("cannot find #string.contains with this type " + types.Type(val.Type).Label()) } } + +func callArgTypeIs(c *compiler, call *parser.Call, id string, argName string, idx int, types ...types.Type) (*llx.Primitive, error) { + if len(call.Function) <= idx { + return nil, errors.New("function " + id + " is missing a " + argName + " (arg #" + strconv.Itoa(idx+1) + ")") + } + + arg := call.Function[idx] + if arg.Value == nil || arg.Value.Operand == nil { + return nil, errors.New("function " + id + " is missing a " + argName + " (arg #" + strconv.Itoa(idx+1) + " is null)") + } + + val, err := c.compileOperand(arg.Value.Operand) + if err != nil { + return nil, err + } + + valType, err := c.dereferenceType(val) + if err != nil { + return nil, err + } + + for _, t := range types { + if t == valType { + return val, nil + } + } + + var typesStr string + for _, t := range types { + typesStr += t.Label() + "/" + } + return nil, errors.New("function " + id + " type mismatch for " + argName + " (expected: " + typesStr[0:len(typesStr)-1] + ", got: " + valType.Label() + ")") +} + +func compileInRange(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { + if call == nil || len(call.Function) != 2 { + return types.Nil, errors.New("function " + id + " needs two arguments (function missing)") + } + + min, err := callArgTypeIs(c, call, id, "min", 0, types.Int, types.Float, types.Dict) + if err != nil { + return types.Nil, err + } + max, err := callArgTypeIs(c, call, id, "max", 1, types.Int, types.Float, types.Dict) + if err != nil { + return types.Nil, err + } + + c.addChunk(&llx.Chunk{ + Call: llx.Chunk_FUNCTION, + Id: "inRange", + Function: &llx.Function{ + Type: string(types.Bool), + Binding: ref, + Args: []*llx.Primitive{min, max}, + }, + }) + return types.Bool, nil +} diff --git a/providers/core/resources/mql_test.go b/providers/core/resources/mql_test.go index ed54b6af34..bc0331a188 100644 --- a/providers/core/resources/mql_test.go +++ b/providers/core/resources/mql_test.go @@ -364,6 +364,12 @@ func TestNumber_Methods(t *testing.T) { { Code: "1 == NaN", Expectation: false, }, + { + Code: "2.inRange(1,2.0)", Expectation: true, + }, + { + Code: "3.0.inRange(1.0,2)", Expectation: false, + }, }) }