All you need to know about Tables in Ballerina!
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 arecord
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 amap
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 usingCustomerTable
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 usingNamesTable
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.
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!! 🙋🏽