Recurse
Imagine you have a large JSON object with users scattered throughout it. They may be found across different paths:
{
"serviceUsers": [
{
"name": "bob",
"service": "clicker"
},
...
],
"org": {
"admins": [
{
"name": "joy",
"teamMembers": [ ... ]
}
...
Given the above example, how can we find every user in this object? With the new recurse
method this question can be answered easily.
Imagine that structure is parsed into a jdata
object.
jdata = parse.json("data.json").params;
Given that users have a name
field defined, we could look for those across all objects and paths:
> jdata.recurse( name != empty )
[
0: {
name: "bob",
service: "clicker"
},
1: {
name: "joy",
teamMembers: [ ... ]
},
...
]
From here, you can search or modify results further. For example, we could be looking for all users that have teamMembers
defined:
> jdata.recurse( name != empty ).where( teamMembers != empty )
We could also grab fields for further processing:
> jdata.recurse( name != empty ).map( name )
[
0: "bob"
1: "joy"
...
]
Find and fix the security risks that pose the biggest threat to your business.
Named function arguments
MQL generally maps all fields you access to the object that is calling it. For example, when you use keywords like where
, their filter function is bound to each object in the list:
> users.where( name != /bob/i )
Implicitly, we are calling name
on each user
object that the list looks at. Under the hood, the inner function looks like this:
user.name != /bob/i
MQL only had two ways to access this bound object. One was the implicit _
identifier, which points to the bound object:
> users.where( _.name != /bob/i )
The second way only applies to maps and is called via key
or value
to access their data:
> {name: "bob", id: "bid"}.where( value == "bob" )
{
name: "bob"
}
However, we noticed that an important use case wasn't covered by these methods: nested recursion.
For example, imagine you have a list of groups and want to find entries whose members contained a user with the same name as the group name. Thanks to named function arguments, we can now cover this use case:
> groups.where(g: g.members.contains(u: u.name == g.name))
groups.where.list: [
0: group name="root" gid=0
]
Let's take this apart: We are looking for a set of groups that match our criteria. The outer loop contains:
> groups.where(g: g.members.contains(u: u.name == g.name))
groups.where.list: [
0: group name="root" gid=0
]
Here we define the argument g
, which means that whenever we call this identifier, we are accessing the group that the loop is looking at.
Next, we look at the members of each group g
:
groups.where(g:
...
)
The members
field is a list of users that are assigned to this group. Again, we define a named argument, in this case u
, which allows us to access each user in this list.
Finally, we add the filter condition and make use of the group g
and the member user u
in each loop:
u.name == g.name
If we bring this all together with nice formatting, we get:
groups.where(g:
g.members.contains(u:
u.name == g.name
)
)
The use of named function arguments isn't limited to nested loops. You can use it anywhere in MQL to help with the readability of the code:
packages.where(package:
package.name == /ssh/i
)
Semver
One of the most requested features in MQL, as early as our first alpha releases, was intelligent operations for comparing versions. It took us some time to get here, but we finally have native support for semantic versions in MQL.
To use semantic versions, you can wrap any string with the new semver
keyword
> semver("1.2.3")
semver: 1.2.3
Semver is very useful when comparing versions with each other. Without semantic versions, we often get unexpected results like this:
> "1.2.3" < "1.12"
# false
With semantic versions, however, this behaves correctly:
> semver("1.2.3") < semver("1.12")
# true
We have also added support for automatically converting strings to semver if only one side of the operation is a string.