diff --git a/llx/builtin_global.go b/llx/builtin_global.go index 9cdbb0b3bf..7419adc51b 100644 --- a/llx/builtin_global.go +++ b/llx/builtin_global.go @@ -34,10 +34,17 @@ func init() { "switch": switchCallV2, "score": scoreCallV2, "typeof": typeofCallV2, - "semver": semverCall, "{}": blockV2, "return": returnCallV2, "createResource": globalCreateResource, + // type-conversions + "string": stringCall, + "$regex": regexCall, // TODO: support both the regex resource and the internal typemap! + "float": floatCall, + "int": intCall, + "bool": boolCall, + "dict": dictCall, + "semver": semverCall, } } @@ -199,6 +206,168 @@ func semverCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, er return &RawData{Type: types.Semver, Value: res.Value}, 0, nil } +func stringCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { + if len(f.Args) != 1 { + return nil, 0, errors.New("Called `string` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + } + + res, dref, err := e.resolveValue(f.Args[0], ref) + if err != nil || dref != 0 || res == nil { + return res, dref, err + } + + switch v := res.Value.(type) { + case string: + return StringData(v), 0, nil + case int64: + i := strconv.FormatInt(v, 10) + return StringData(i), 0, nil + case float64: + f := strconv.FormatFloat(v, 'f', 2, 64) + return StringData(f), 0, nil + case bool: + if v { + return StringData("true"), 0, nil + } + return StringData("false"), 0, nil + default: + return NilData, 0, nil + } +} + +func regexCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { + if len(f.Args) != 1 { + return nil, 0, errors.New("Called `regex` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + } + + res, dref, err := e.resolveValue(f.Args[0], ref) + if err != nil || dref != 0 || res == nil { + return res, dref, err + } + + switch v := res.Value.(type) { + case string: + return RegexData(v), 0, nil + case int64: + i := strconv.FormatInt(v, 10) + return RegexData(i), 0, nil + case float64: + f := strconv.FormatFloat(v, 'f', 2, 64) + return RegexData(f), 0, nil + case bool: + if v { + return RegexData("true"), 0, nil + } + return RegexData("false"), 0, nil + default: + return NilData, 0, nil + } +} + +func intCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { + if len(f.Args) != 1 { + return nil, 0, errors.New("Called `int` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + } + + res, dref, err := e.resolveValue(f.Args[0], ref) + if err != nil || dref != 0 || res == nil { + return res, dref, err + } + + switch v := res.Value.(type) { + case string: + i, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, 0, err + } + return IntData(i), 0, nil + case int64: + return IntData(v), 0, nil + case float64: + return IntData(int64(v)), 0, nil + case bool: + if v { + return IntData(1), 0, nil + } + return IntData(0), 0, nil + default: + return NilData, 0, nil + } +} + +func floatCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { + if len(f.Args) != 1 { + return nil, 0, errors.New("Called `float` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + } + + res, dref, err := e.resolveValue(f.Args[0], ref) + if err != nil || dref != 0 || res == nil { + return res, dref, err + } + + switch v := res.Value.(type) { + case string: + i, err := strconv.ParseFloat(v, 64) + if err != nil { + return nil, 0, err + } + return FloatData(i), 0, nil + case int64: + return FloatData(float64(v)), 0, nil + case float64: + return FloatData(v), 0, nil + case bool: + if v { + return FloatData(1), 0, nil + } + return FloatData(0), 0, nil + default: + return NilData, 0, nil + } +} + +func boolCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { + if len(f.Args) != 1 { + return nil, 0, errors.New("Called `bool` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + } + + res, dref, err := e.resolveValue(f.Args[0], ref) + if err != nil || dref != 0 || res == nil { + return res, dref, err + } + + switch v := res.Value.(type) { + case string: + return BoolData(v == "true"), 0, nil + case int64: + return BoolData(v != 0), 0, nil + case float64: + return BoolData(v != 0), 0, nil + case bool: + return BoolData(v), 0, nil + default: + return NilData, 0, nil + } +} + +func dictCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { + if len(f.Args) != 1 { + return nil, 0, errors.New("Called `dict` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + } + + res, dref, err := e.resolveValue(f.Args[0], ref) + if err != nil || dref != 0 || res == nil { + return res, dref, err + } + + switch v := res.Value.(type) { + case string, int64, float64, bool, []any, map[string]any: + return DictData(v), 0, nil + default: + return NilData, 0, nil + } +} + func expectV2(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { if len(f.Args) != 1 { return nil, 0, errors.New("Called expect with " + strconv.Itoa(len(f.Args)) + " arguments, expected 1") diff --git a/llx/builtin_simple.go b/llx/builtin_simple.go index b50dd907e4..f94e6b5630 100644 --- a/llx/builtin_simple.go +++ b/llx/builtin_simple.go @@ -1152,7 +1152,7 @@ func intLTStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (* return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(right.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(left.(int64) < f) }) @@ -1162,7 +1162,7 @@ func intLTEStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) ( return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(right.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(left.(int64) <= f) }) @@ -1172,7 +1172,7 @@ func intGTStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (* return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(right.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(left.(int64) > f) }) @@ -1182,7 +1182,7 @@ func intGTEStringV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) ( return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(right.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(left.(int64) >= f) }) @@ -1194,7 +1194,7 @@ func stringLTIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (* return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(left.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(f < right.(int64)) }) @@ -1204,7 +1204,7 @@ func stringLTEIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) ( return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(left.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(f <= right.(int64)) }) @@ -1214,7 +1214,7 @@ func stringGTIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (* return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(left.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(f > right.(int64)) }) @@ -1224,7 +1224,7 @@ func stringGTEIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) ( return nonNilDataOpV2(e, bind, chunk, ref, types.Bool, func(left interface{}, right interface{}) *RawData { f, err := strconv.ParseInt(left.(string), 10, 64) if err != nil { - return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to float")} + return &RawData{Type: types.Bool, Error: errors.New("failed to convert string to int")} } return BoolData(f >= right.(int64)) }) diff --git a/mqlc/mqlc.go b/mqlc/mqlc.go index 26b865c226..d854e966a5 100644 --- a/mqlc/mqlc.go +++ b/mqlc/mqlc.go @@ -1300,6 +1300,17 @@ func (c *compiler) compileIdentifier(id string, callBinding *variable, calls []* return restCalls, variable.typ, nil } + f = typeConversions[id] + if f != nil { + typ, err := f(c, id, call) + // If it works or is some random error, we are done. However, we + // try to toss this fish back in the sea if it's not a conversion. + // For example: regex.ipv4 can be handled below, since it's not a conversion + if err == nil || err != errNotConversion { + return restCalls, typ, err + } + } + found, restCalls, typ, err = c.compileResource(id, calls) if found { return restCalls, typ, err diff --git a/mqlc/operators.go b/mqlc/operators.go index ea344d3a12..e46cde2616 100644 --- a/mqlc/operators.go +++ b/mqlc/operators.go @@ -43,7 +43,6 @@ func init() { "switch": compileSwitch, "Never": compileNever, "empty": compileEmpty, - "semver": compileSemver, } } @@ -497,33 +496,6 @@ func compileTypeof(c *compiler, id string, call *parser.Call) (types.Type, error return types.String, nil } -func compileSemver(c *compiler, id string, call *parser.Call) (types.Type, error) { - if call == nil || len(call.Function) < 1 { - return types.Nil, errors.New("missing parameter for '" + id + "', it requires 1") - } - - arg := call.Function[0] - if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil { - return types.Nil, errors.New("failed to get parameter for '" + id + "'") - } - - argValue, err := c.compileExpression(arg.Value) - if err != nil { - return types.Nil, err - } - - c.addChunk(&llx.Chunk{ - Call: llx.Chunk_FUNCTION, - Id: "semver", - Function: &llx.Function{ - Type: string(types.String), - Args: []*llx.Primitive{argValue}, - }, - }) - - return types.String, nil -} - func compileSwitch(c *compiler, id string, call *parser.Call) (types.Type, error) { var ref *llx.Primitive diff --git a/mqlc/typemaps.go b/mqlc/typemaps.go new file mode 100644 index 0000000000..11c3e3b2d6 --- /dev/null +++ b/mqlc/typemaps.go @@ -0,0 +1,57 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package mqlc + +import ( + "errors" + + "go.mondoo.com/cnquery/v10/llx" + "go.mondoo.com/cnquery/v10/mqlc/parser" + "go.mondoo.com/cnquery/v10/types" +) + +var typeConversions map[string]fieldCompiler + +func init() { + typeConversions = map[string]fieldCompiler{ + "bool": compileTypeConversion("bool", types.Bool), + "int": compileTypeConversion("int", types.Int), + "float": compileTypeConversion("float", types.Float), + "string": compileTypeConversion("string", types.String), + "regex": compileTypeConversion("$regex", types.Regex), + "dict": compileTypeConversion("dict", types.Dict), + "semver": compileTypeConversion("semver", types.Semver), + } +} + +var errNotConversion = errors.New("not a type-conversion") + +func compileTypeConversion(llxID string, typ types.Type) fieldCompiler { + return func(c *compiler, id string, call *parser.Call) (types.Type, error) { + if call == nil || len(call.Function) < 1 { + return types.Nil, errNotConversion + } + + arg := call.Function[0] + if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil { + return types.Nil, errors.New("failed to get parameter for '" + id + "'") + } + + argValue, err := c.compileExpression(arg.Value) + if err != nil { + return types.Nil, err + } + + c.addChunk(&llx.Chunk{ + Call: llx.Chunk_FUNCTION, + Id: llxID, + Function: &llx.Function{ + Type: string(typ), + Args: []*llx.Primitive{argValue}, + }, + }) + + return types.String, nil + } +}