Skip to content

Commit 3475d2c

Browse files
authored
✨ array.reverse() and array.join() (#6058)
Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
1 parent 45b4ef1 commit 3475d2c

File tree

5 files changed

+106
-0
lines changed

5 files changed

+106
-0
lines changed

llx/builtin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,8 @@ func init() {
633633
"$one": {f: arrayOneV2},
634634
"map": {f: arrayMapV2},
635635
"flat": {f: arrayFlat},
636+
"reverse": {f: arrayReverse},
637+
"join": {f: arrayJoin},
636638
"duplicates": {f: arrayDuplicatesV2},
637639
"fieldDuplicates": {f: arrayFieldDuplicatesV2},
638640
"unique": {f: arrayUniqueV2},

llx/builtin_array.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"math/rand"
99
"strconv"
10+
"strings"
1011

1112
"go.mondoo.com/cnquery/v12/types"
1213
"go.mondoo.com/cnquery/v12/utils/multierr"
@@ -506,6 +507,63 @@ func arrayFlat(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawD
506507
return &RawData{Type: types.Array(typ), Value: res}, 0, nil
507508
}
508509

510+
func arrayReverse(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
511+
if bind.Value == nil {
512+
return &RawData{Type: bind.Type, Error: bind.Error}, 0, nil
513+
}
514+
515+
list, ok := bind.Value.([]any)
516+
// this should not happen at this point
517+
if !ok {
518+
return &RawData{Type: bind.Type, Error: errors.New("incorrect type, no array data found")}, 0, nil
519+
}
520+
521+
res := make([]any, len(list))
522+
for i := range list {
523+
res[len(res)-1-i] = list[i]
524+
}
525+
526+
return &RawData{Type: bind.Type, Value: res}, 0, nil
527+
}
528+
529+
func arrayJoin(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
530+
if bind.Value == nil {
531+
return &RawData{Type: bind.Type, Error: bind.Error}, 0, nil
532+
}
533+
534+
list, ok := bind.Value.([]any)
535+
// this should not happen at this point
536+
if !ok {
537+
return &RawData{Type: bind.Type, Error: errors.New("incorrect type, no array data found")}, 0, nil
538+
}
539+
540+
var sep string
541+
if len(chunk.Function.Args) == 1 {
542+
item, rref, err := e.resolveValue(chunk.Function.Args[0], ref)
543+
if err != nil || rref > 0 {
544+
return nil, rref, err
545+
}
546+
sep, ok = item.Value.(string)
547+
if !ok {
548+
return nil, rref, errors.New("separator provided to join() must be a string")
549+
}
550+
}
551+
552+
var res strings.Builder
553+
for i := range list {
554+
s, ok := list[i].(string)
555+
if !ok {
556+
return nil, 0, errors.New("failed to join() elements, must be strings")
557+
}
558+
if sep != "" && i > 0 {
559+
res.WriteString(sep)
560+
}
561+
res.WriteString(s)
562+
}
563+
564+
return &RawData{Type: types.String, Value: res.String()}, 0, nil
565+
}
566+
509567
// Take an array and separate it into a list of unique entries and another
510568
// list of only duplicates. The latter list only has every entry appear only
511569
// once.

mqlc/builtin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ func init() {
209209
"none": {compile: compileArrayNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}},
210210
"map": {compile: compileArrayMap, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}},
211211
"flat": {compile: compileArrayFlat, signature: FunctionSignature{}},
212+
"reverse": {typHandler: &sameType, signature: FunctionSignature{}},
213+
"join": {compile: compileArrayJoin, signature: FunctionSignature{Args: []types.Type{types.String}}},
212214
},
213215
types.MapLike: {
214216
"[]": {typHandler: &childType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.String}}},

mqlc/builtin_array.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,35 @@ func compileArrayFlat(c *compiler, typ types.Type, ref uint64, id string, call *
526526
})
527527
return typ, nil
528528
}
529+
530+
func compileArrayJoin(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) {
531+
switch typ.Child() {
532+
case types.String, types.Dict:
533+
default:
534+
return types.Nil, errors.New("can only call join() on arrays that have strings in them")
535+
}
536+
537+
var args []*llx.Primitive = nil
538+
if call != nil {
539+
if len(call.Function) > 1 {
540+
return types.Nil, errors.New("called join() with too many arguments, at most one supported")
541+
}
542+
prim, err := callArgTypeIs(c, call, id, "separator", 0, types.String, types.Dict)
543+
if err != nil {
544+
return types.Nil, err
545+
}
546+
args = []*llx.Primitive{prim}
547+
}
548+
549+
c.addChunk(&llx.Chunk{
550+
Call: llx.Chunk_FUNCTION,
551+
Id: id,
552+
Function: &llx.Function{
553+
Type: string(typ),
554+
Binding: ref,
555+
Args: args,
556+
},
557+
})
558+
559+
return types.String, nil
560+
}

providers/core/resources/mql_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,10 @@ func TestString_Methods(t *testing.T) {
471471
Code: "'hello world'.split(' ')",
472472
Expectation: []any{"hello", "world"},
473473
},
474+
{
475+
Code: "'1.2.3.4'.split('.').reverse.join(':')",
476+
Expectation: "4:3:2:1",
477+
},
474478
{
475479
Code: "'he\nll\no'.lines",
476480
Expectation: []any{"he", "ll", "o"},
@@ -574,6 +578,14 @@ func TestArray(t *testing.T) {
574578
Code: "[1,2,3]",
575579
Expectation: []any{int64(1), int64(2), int64(3)},
576580
},
581+
{
582+
Code: "[1,2,3].length",
583+
Expectation: int64(3),
584+
},
585+
{
586+
Code: "[1,2,3].reverse",
587+
Expectation: []any{int64(3), int64(2), int64(1)},
588+
},
577589
{
578590
Code: "return [1,2,3]",
579591
Expectation: []any{int64(1), int64(2), int64(3)},

0 commit comments

Comments
 (0)