go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mqlc/builtin.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package mqlc 5 6 import ( 7 "errors" 8 9 "go.mondoo.com/cnquery/llx" 10 "go.mondoo.com/cnquery/mqlc/parser" 11 "go.mondoo.com/cnquery/providers-sdk/v1/resources" 12 "go.mondoo.com/cnquery/types" 13 ) 14 15 type compileHandler struct { 16 typ func(types.Type) types.Type 17 signature FunctionSignature 18 compile func(*compiler, types.Type, uint64, string, *parser.Call) (types.Type, error) 19 } 20 21 var ( 22 childType = func(t types.Type) types.Type { return t.Child() } 23 arrayBlockType = func(t types.Type) types.Type { return types.Array(types.Map(types.Int, types.Block)) } 24 boolType = func(t types.Type) types.Type { return types.Bool } 25 intType = func(t types.Type) types.Type { return types.Int } 26 stringType = func(t types.Type) types.Type { return types.String } 27 stringArrayType = func(t types.Type) types.Type { return types.Array(types.String) } 28 dictType = func(t types.Type) types.Type { return types.Dict } 29 blockType = func(t types.Type) types.Type { return types.Block } 30 dictArrayType = func(t types.Type) types.Type { return types.Array(types.Dict) } 31 ) 32 33 var builtinFunctions map[types.Type]map[string]compileHandler 34 35 func init() { 36 builtinFunctions = map[types.Type]map[string]compileHandler{ 37 types.String: { 38 "contains": {compile: compileStringContains, typ: boolType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}}, 39 "find": {typ: stringArrayType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Regex}}}, 40 "length": {typ: intType, signature: FunctionSignature{}}, 41 "camelcase": {typ: stringType, signature: FunctionSignature{}}, 42 "downcase": {typ: stringType, signature: FunctionSignature{}}, 43 "upcase": {typ: stringType, signature: FunctionSignature{}}, 44 "lines": {typ: stringArrayType, signature: FunctionSignature{}}, 45 "split": {typ: stringArrayType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}}, 46 "trim": {typ: stringType, signature: FunctionSignature{Required: 0, Args: []types.Type{types.String}}}, 47 }, 48 types.Time: { 49 "seconds": {typ: intType, signature: FunctionSignature{}}, 50 "minutes": {typ: intType, signature: FunctionSignature{}}, 51 "hours": {typ: intType, signature: FunctionSignature{}}, 52 "days": {typ: intType, signature: FunctionSignature{}}, 53 "unix": {typ: intType, signature: FunctionSignature{}}, 54 }, 55 types.Dict: { 56 "[]": {typ: dictType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Any}}}, 57 "{}": {typ: blockType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 58 // string-ish 59 "find": {typ: stringArrayType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Regex}}}, 60 "length": {typ: intType, signature: FunctionSignature{}}, 61 "camelcase": {typ: stringType, signature: FunctionSignature{}}, 62 "downcase": {typ: stringType, signature: FunctionSignature{}}, 63 "upcase": {typ: stringType, signature: FunctionSignature{}}, 64 "lines": {typ: stringArrayType, signature: FunctionSignature{}}, 65 "split": {typ: stringArrayType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}}, 66 "trim": {typ: stringType, signature: FunctionSignature{Required: 0, Args: []types.Type{types.String}}}, 67 // array- or map-ish 68 "first": {typ: dictType, signature: FunctionSignature{}}, 69 "last": {typ: dictType, signature: FunctionSignature{}}, 70 "where": {compile: compileDictWhere, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 71 "contains": {compile: compileDictContains, typ: boolType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 72 "containsOnly": {compile: compileDictContainsOnly, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 73 "containsNone": {compile: compileDictContainsNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 74 "all": {compile: compileDictAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 75 "any": {compile: compileDictAny, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 76 "one": {compile: compileDictOne, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 77 "none": {compile: compileDictNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 78 "map": {compile: compileArrayMap, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 79 "flat": {compile: compileDictFlat, signature: FunctionSignature{}}, 80 // map-ish 81 "keys": {typ: stringArrayType, signature: FunctionSignature{}}, 82 "values": {typ: dictArrayType, signature: FunctionSignature{}}, 83 }, 84 types.ArrayLike: { 85 "[]": {typ: childType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Int}}}, 86 "first": {typ: childType, signature: FunctionSignature{}}, 87 "last": {typ: childType, signature: FunctionSignature{}}, 88 "{}": {typ: arrayBlockType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 89 "length": {typ: intType, signature: FunctionSignature{}}, 90 "where": {compile: compileArrayWhere, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 91 "duplicates": {compile: compileArrayDuplicates, signature: FunctionSignature{Required: 0, Args: []types.Type{types.String}}}, 92 "unique": {compile: compileArrayUnique, signature: FunctionSignature{Required: 0}}, 93 "contains": {compile: compileArrayContains, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 94 "containsOnly": {compile: compileArrayContainsOnly, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 95 "containsNone": {compile: compileArrayContainsNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 96 "all": {compile: compileArrayAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 97 "any": {compile: compileArrayAny, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 98 "one": {compile: compileArrayOne, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 99 "none": {compile: compileArrayNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 100 "map": {compile: compileArrayMap, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 101 "flat": {compile: compileArrayFlat, signature: FunctionSignature{}}, 102 }, 103 types.MapLike: { 104 "[]": {typ: childType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}}, 105 "{}": {typ: blockType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 106 "length": {typ: intType, signature: FunctionSignature{}}, 107 "where": {compile: compileMapWhere, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 108 "keys": {typ: stringArrayType, signature: FunctionSignature{}}, 109 "values": {compile: compileMapValues, signature: FunctionSignature{}}, 110 }, 111 types.ResourceLike: { 112 // "": compileHandler{compile: compileResourceDefault}, 113 "length": {compile: compileResourceLength, signature: FunctionSignature{}}, 114 "where": {compile: compileResourceWhere, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 115 "contains": {compile: compileResourceContains, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 116 "all": {compile: compileResourceAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 117 "any": {compile: compileResourceAny, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 118 "one": {compile: compileResourceOne, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 119 "none": {compile: compileResourceNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 120 "map": {compile: compileResourceMap, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, 121 }, 122 // TODO: [#32] unique builtin fields that need a long-term support in LR 123 types.Resource("parse"): { 124 "date": {compile: compileResourceParseDate, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String, types.String}}}, 125 }, 126 } 127 } 128 129 // Note: Call it with the full type, not just the underlying type 130 func builtinFunction(typ types.Type, id string) (*compileHandler, error) { 131 // TODO: [#32] special handlers for specific types, which are builtin and should 132 // be removed long-term, one the resource is native in LR 133 fh, ok := builtinFunctions[typ] 134 if ok { 135 c, ok := fh[id] 136 if ok { 137 return &c, nil 138 } 139 } 140 141 fh, ok = builtinFunctions[typ.Underlying()] 142 if ok { 143 c, ok := fh[id] 144 if ok { 145 return &c, nil 146 } 147 } else { 148 return nil, errors.New("cannot find any functions for type '" + typ.Label() + "' during compile") 149 } 150 151 return nil, errors.New("cannot find function '" + id + "' for type '" + typ.Label() + "' during compile") 152 } 153 154 // Compile calls to builtin type handlers, that aren't mapped via builtin 155 // functions above. Typically only used if we need to go deeper into the given 156 // type to figure out what to do. For example: list resources are just 157 // resource types, so we can't tell if there are builtin functions without 158 // detecting that we are looking at a list resource. 159 func (c *compiler) compileImplicitBuiltin(typ types.Type, id string) (*compileHandler, *variable, error) { 160 if !typ.IsResource() { 161 return nil, nil, nil 162 } 163 164 r := typ.ResourceName() 165 resource := c.Schema.Lookup(r) 166 if resource == nil || resource.ListType == "" { 167 return nil, nil, nil 168 } 169 170 ch, ok := builtinFunctions[types.ArrayLike][id] 171 if !ok { 172 return nil, nil, nil 173 } 174 175 resType := types.Array(types.Type(resource.ListType)) 176 c.addChunk(&llx.Chunk{ 177 Call: llx.Chunk_FUNCTION, 178 Id: "list", 179 Function: &llx.Function{ 180 Type: string(resType), 181 Binding: c.tailRef(), 182 }, 183 }) 184 return &ch, &variable{ 185 typ: resType, 186 ref: c.tailRef(), 187 }, nil 188 } 189 190 func publicFieldsInfo(c *compiler, resourceInfo *resources.ResourceInfo) map[string]llx.Documentation { 191 res := map[string]llx.Documentation{} 192 for k, v := range resourceInfo.Fields { 193 if v.IsPrivate { 194 continue 195 } 196 if v.IsEmbedded && !c.UseAssetContext { 197 continue 198 } 199 200 if v.IsEmbedded && c.UseAssetContext { 201 name := types.Type(v.Type).ResourceName() 202 child := c.Schema.Lookup(name) 203 if child == nil { 204 continue 205 } 206 childFields := publicFieldsInfo(c, child) 207 for k, v := range childFields { 208 res[k] = v 209 } 210 continue 211 } 212 213 if v.IsImplicitResource { 214 name := types.Type(v.Type).ResourceName() 215 child := c.Schema.Lookup(name) 216 if !child.HasEmptyInit() { 217 continue 218 } 219 220 // implicit resources don't have their own metadata, so we grab it from 221 // the resource itself 222 res[k] = llx.Documentation{ 223 Field: k, 224 Title: child.Title, 225 Desc: child.Desc, 226 } 227 continue 228 } 229 230 res[k] = llx.Documentation{ 231 Field: k, 232 Title: v.Title, 233 Desc: v.Desc, 234 } 235 } 236 237 return res 238 } 239 240 // Glob {*} all fields for a given type. Note, that this descends into 241 // list elements of array resources if permitted. 242 func availableGlobFields(c *compiler, typ types.Type, descend bool) map[string]llx.Documentation { 243 var res map[string]llx.Documentation 244 245 if !typ.IsResource() { 246 return res 247 } 248 249 resourceInfo := c.Schema.Lookup(typ.ResourceName()) 250 if descend && resourceInfo.ListType != "" { 251 base := types.Type(resourceInfo.ListType).ResourceName() 252 if info := c.Schema.Lookup(base); info != nil { 253 resourceInfo = info 254 } 255 } 256 257 return publicFieldsInfo(c, resourceInfo) 258 } 259 260 func availableFields(c *compiler, typ types.Type) map[string]llx.Documentation { 261 var res map[string]llx.Documentation 262 263 // resources maintain their own fields and may be list resources 264 if typ.IsResource() { 265 resourceInfo := c.Schema.Lookup(typ.ResourceName()) 266 res = publicFieldsInfo(c, resourceInfo) 267 268 _, err := listResource(c, typ) 269 if err == nil { 270 m := builtinFunctions[typ.Underlying()] 271 for k := range m { 272 res[k] = llx.Documentation{ 273 Field: k, 274 } 275 } 276 } 277 278 } 279 280 // We first try to auto-complete the full type. This is important for 281 // more complex types, like resource types (eg `parse`). 282 builtins := builtinFunctions[typ] 283 if builtins == nil && res == nil { 284 // Only if we fail to find the full resource AND if we couldn't look 285 // up the resource definition either, will we look for additional 286 // methods. Otherwise we stick to the directly defined methods, not any 287 // potentially "shared" methods (which aren't actually shared). 288 builtins = builtinFunctions[typ.Underlying()] 289 if builtins == nil { 290 return res 291 } 292 } 293 294 // the non-resource use-case: 295 if res == nil { 296 res = make(map[string]llx.Documentation, len(builtins)) 297 } 298 299 for k := range builtins { 300 res[k] = llx.Documentation{ 301 Field: k, 302 } 303 } 304 305 return res 306 }