Reference
Before diving into different kinds of operators, we will first look the steps involved in running a Rhyme query. We assume that you have already installed the rhyme-lang
package in your project. If not, please head over to the installation page.
Rhyme works by compiling the given query into a function that can be run on data. The compilation step is done by the api.compile
function. This returns a JS function which can be run on any input data to get the result of the query.
// import the library
let { api } = require("rhyme-lang")
// define the query
let query = rh`{ data.*.key: sum(data.*.value) }`
// use the api.compile function to compile the query
let func = api.compile(query)
// run the compiled function on some sample data
let sampleData = [
{ key: "A", value: 10 },
{ key: "B", value: 20 },
{ key: "A", value: 30 }
]
let result = func({"data": sampleData})
Now that we have seen the basic steps involved in running a Rhyme query, let us look at the different kinds of operators available in Rhyme.
Metaprogramming API
We have seen above how to use the rh`{ data.*.key: ...}`
syntax to specify a Rhyme query.
To facilitate metaprogramming and composition of queries in JS, Rhyme also allows to specify the query using JS objects directly:
let query = {
"data.*.key": api.sum("data.*.value")
}
Simple Indexing
Like JQ, we can use the .
operator to index into objects and arrays. For instance the query data.0
will return the first element of the array data
.
Iterating
We can use the *
operator to iterate over arrays and compute aggregations. For instance, the query data.*.value
will iterate through data
and collect all the value
attributes.
[data.*.value] // collect all the values
Note that if no top-level aggregation is specified explicitly, the first value is returned. For instance, the query data.*.value
will return 10
for the sample data above.
We will see later that these iterators can be named like *A
, *B
, *item
, etc. instead of using the default *
symbol to allow for more complex queries.
Aggregations
We can use aggregate operators to compute aggregations over iterated values. For instance, the query sum(data.*.value)
will compute the sum of all the values. Below is a list of all the aggregate operators currently available in Rhyme.
Aggregation | Description |
---|---|
sum | Computes the sum of the values |
min | Computes the minimum of the values |
max | Computes the maximum of the values |
mean | Computes the mean of the values |
count | Computes the number of values |
array or [ ] | Collects all the values into an array |
Arithmetic (Binary Operators)
Rhyme supports the usual basic arithmetic operations like, addition, subtraction, multiplication, division, etc.
Grouping
Group-bys in Rhyme are implicitly expressed using keys. For instance, having data.*.key
as the key would do a group-by on the key
attribute of the data and whenever data.*
is iterated within this key, it will only iterate over the values that have the same key. We can use nested JSON objects to express group-bys on multiple keys. For instance, the query below will do a group-by on the key1
and key2
attributes of the data.
{
data.*.key1: {
data.*.key2: sum(data.*.value)
}
}
Joins
Rhyme supports efficient equi-joins in a natural way. Joins between two objects are expressed as lookup on the other object based on the key. The following query will do a join between data1
and data2
based on the key
attribute and compute the sum of products of the value
attributes.
{
data2.(data1.*.key): sum(data2.*.value * data1.*.value)
}
This example assumes that data2
can be directly indexed by the key
field of data1
. If this is not the case, data2
needs to be grouped by the matching key (as shown above) beforehand.
UDFs
Rhyme supports user-defined functions (UDFs) which can be used to express custom computations. UDFs are expressed as using normal JS functions that are passed as additional input when the query is run. For instance, the following query will apply the multiply10
function to all the values in data.*.value
.
// define udfs
let udf = { multiply10: x => x * 10 }
// define query
let query = rh`udf.multiply10(data.*.value)`
// compile query
let func = api.compile(query)
// pass udf as additional input when running query
let result = func({"data": sampleData, "udf": udf})
Generated Code
After compiling a Rhyme query, we can inspect the generated code:
// define the query
let query = rh`{ data.*.key: sum(data.*.value) }`
// use the api.compile function to compile the query
let func = api.compile(query)
// print generated code
console.log(func.explain.codeString)
Show output
inp => {
let tmp = {}
tmp[0] ??= {}
for (let x in inp['data']) {
tmp[0][inp['data'][x]['key']] ??= 0
tmp[0][inp['data'][x]['key']] += inp['data'][x]['value']
}
return tmp[0]
}