All you need to know about Tables in Ballerina!

Lasini Liyanage
6 min readMay 30, 2021

Hello there! 👋 Having a good time exploring Ballerina?? Yeah.. Thought so..😃 Then, without further ado let’s get started. In this article, I’ll be introducing you to the table concept in Ballerina.

As you may have already noticed Ballerina has several basic types which are categorized under simple, sequence, structural and behavioral types. table is one of the structural basic types.

Structured values are containers for other values, which are called their members.

What is a Table?

A table is a structured value in Ballerina. It is an ordered collection of mapping values. But it does not allow randomly accessing its members using their positional information. Rather it uses a key to uniquely identify its members within the table and access them when required. The key is a combination of one or more read-only fields derived from the member mapping value.

Table Type

The language specification for creating a table type descriptor is as follows.

table-type-descriptor := table row-type-parameter [key-constraint]
row-type-parameter := <type-descriptor>
key-constraint := key-specifier | key-type-constraint
key-specifier := key ( [ field-name (, field-name)* ] )
key-type-constraint := key <type-descriptor>

First, you have the “table” keyword. Then you need to specify the row-type-parameter which is the shape to which your table members belong. The type you give here should always be a subtype of map<any|error>. Hence a table is always constrained by either a record type or a map type. Specifying the key-constraint is optional. If you don’t specify the key-constraint it would be a keyless table while specifying the key-constraint provides you a keyed table.

The key-constraint can either be specified as a key-specifier or a key-type-constraint. A key-specifier consists of “key” keyword accompanied by the field names of any required and read-only fields of the record type constrained by the table, which are used to uniquely identify each member. The static type of each of these key fields should be a subtype of anydata. A key-type-constraint consists of “key” keyword accompanied by the type of the keys.

Ballerina Swan Lake Beta1 is out!!! 🎉 🎉 🎉 Try out the examples in this blog post using Beta1….

  • Let’s create a table type constrained with a record type.

The CustomerTable has Customer type members which can be uniquely identified using their customerId. <Customer> is the row-type-parameter and key(customerId) is the key-specifier.

Here the key-constraint is given as a key-type-constraint using key<int>.

  • Let’s create a table type constrained with a map type.

A table constrained with a map type cannot have a key-specifier or a key-type-constraint.

Table Value

You can use a table constructor expression to create a new table value. A table constructor works like a list constructor.

table-constructor-expr := table [key-specifier] [ [row-list] ]
row-list := mapping-constructor-expr (, mapping-constructor-expr)*

Providing the key-specifier is optional. If a key-specifier is not specified, then the key sequence of the table members comes from the key-specifier of the applicable contextually expected type. If both are present, then they must be the same. If neither of them is present, then the key sequence is empty. The members of the table come from evaluating each mapping-constructor-expr in the row-list in order.

  • Let’s create a table value using CustomerTable table type.

CustomerTable is the contextually expected type. Therefore the key sequence is derived from the key-specifier of the CustomerTable. Each table member is aCustomer type value, uniquely identified using its customerId.

When CustomerTable type has specified a key-type-constraint, it is mandatory to provide the key-specifier in the table constructor expression. If not given, it would result in a compile-time error. Consider the following example. customerId is given as the key for tbl using key(customerId).

  • Let’s create a table value using NamesTable table type.

Here the contextually expected type is NamesTable. Since a table constrained with a map type cannot have a key-constraint you can not specify a key-specifier in the table constructor expression. Hence the key sequence is empty for tbl. tbl is a keyless table value.

Multiple Key Fields

A table can have multiple key fields. The key sequence consists of all the specified fields and a table member is uniquely identified using the aggregation of all the key field values.

Each customer member in tbl is identified using their (customerId,name) value. It is a compile-time error if two or more rows of the table have the same key value.

Compile-time error when there are duplicate keys

Accessing Table Members

Member access can be performed on table values using their key values. If the table does not contain a member with the specified key, the result is (), else it returns the member with the given key.

The customer with the key [3, “John”] is accessed using tbl[[3, “John”]]. The output of the above program is,

{"customerId":3,"name":"John","noOfItems":5}
No such Customer

Langlib Functions for Tables

lang.table module provides fundamental operations that can be performed on table values.

Langlib is small library defined by language providing fundamental operations on built-in datatypes.

The output of the above example is,

{"customerId":4,"name":"John","noOfItems":7}
{"customerId":3,"name":"John","noOfItems":5}

A table is iterable. The iteration sequence consists of the members of the table in order and the iteration completion value is always nil. Therefore tables support functional iteration operations such as foreach, map, filter and reduce too.

There are many more useful functions defined in the lang.table module.

Querying tables

Tables can be iterated using the from clause and join clause in query expressions. The type of the final result is determined by the contextually expected type or the input type.

You can read my previous blog post to find out how query expressions work! 😄

The from clause iterates the tbl similar to a foreach statement. The above query expression selects the numberOfItems of each customer who has purchased more than 10 items (cust.noOfItems>10) and order the results using the name of the customers (cust.name) in ascending order. The contextually expected type of the query expression is int[]. Therefore the result is an “int array”.

Output:

[20,15]

Let’s look at another example.

In the above example, the type of the final result is determined by the input type. Since the input for the above query expression is a table, the result of the above query expression is expected to be a table having Customer type members.

Output:

typedesc table<Customer>
[{"customerId":2,"name":"Anne","noOfItems":20,"address":"Colombo"},{"customerId":1,"name":"Mike","noOfItems":15}]

Constructing Tables with Query Expressions

Adding the “table” keyword with a key-specifier as a prefix to a query expression allows users to create a table from the query expression.

Here, the query expression creates a table having Order type members who are uniquely identified using their orderId.

Output:

[{"orderId":1,"customerId":3,"amount":0.0}]

And yeah…That’s all you need to know about tables in Ballerina! Hope you now find it easy to work with tables! 😁 Don’t forget to go through the Ballerina website if you want to find out about more exciting features. 😇

Till we meet again, Adiós!! 🙋🏽

References

--

--