Skip to main content

Associate objects by a key

In this guide we will build a simple function for creating a map that collects a structure based on a certain key. This is a standard pattern when working with many-to-one relations.

Say that you have for example the entity relations:

entity house {
key id: integer;
color: text;
}

entity street {
key address: text;
}

entity street_house {
key house;
index street;
}

For this relation, each house is unique and lives on a street, but each street can contain several houses. If we want to create a query that returns all houses grouped by their street, we'd like to get the following signature:

query get_houses_by_street(): map<text, list<struct<house>>>

That is, each entry in the map has the address as key, and a list of house-structs as values. Performing a database query using the at-operator, we can at most get a list<text, struct<house>>:

val house_list: list<(text, struct<house>)> = street_house @* {} (.street.address, .house.to_struct());

So how do we create a map for this structure? Since lamba functions are not yet supported by Rell, we must iterate through the list and create the map ourselves. Let's create a function for this:

function group_house_by_address(value: list<(text, struct<house>)>) {
val result = map<text, list<struct<house>>>();
for ((address, house) in value) {
if (address not in result) result[address] = list<struct<house>>();
result[address].add(house);
}
return result;
}
warning

Be careful not to use eny entity references in such function as it may result in excessive database roundtrips. In the above example we use primitive types (text) and in-memory structures (struct<house>) which will be most performant.

Now we can use this function in our query:

query get_houses_by_street(): map<text, list<struct<house>>> {
val house_list: list<(text, struct<house>)> = street_house @* {} (.street.address, .house.to_struct());
return group_house_by_address(house_list);
}
Recipe for associating arrays with a property

Although Rell does not support generics, we can create a recipe based on the group_house_by_address function that we can implement when we need it.

// Replace V and K with your actual types
function group_V_by_K(value: list<(K, V)>) {
val result = map<K, list<V>>();
for ((k, v) in value) {
if (k not in result) result[k] = list<V>();
result[k].add(v);
}
return result;
}
Grouping other structures

The above example is not even limited to list<(K, V)> types. We can of course specialize the pattern to work with list<struct> types as well. For example, grouping houses by color:

function group_house_by_color(value: list<struct<house>>) {
val result = map<text, list<struct<house>>>();
for (house in value) {
if (house.color not in result) result[house.color] = list<V>();
result[house.color].add(house);
}
return result;
}