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  }