Ballerina 1.0 Experimental Features

Primary contributors:

Copyright © 2018, 2019 WSO2

Licensed under the Creative Commons Attribution-NoDerivatives 4.0 International license

Table of contents

1. Introduction

This document describes features implemented experimentally in Ballerina 1.0.

2. Values, types and variables

2.1 Behavioral values

behavioral-type-descriptor :=
   ...
   | stream-type-descriptor

2.1.1 Streams

stream-type-descriptor := stream [type-parameter]

A value of type stream<T> is a distributor for values of type T: when a value v of type T is put into the stream, a function will be called for each subscriber to the stream with v as an argument. T must be a pure type.

A value belongs to a type stream (without the type-parameter) if it has basic type stream.

A new stream can be constructed using a new expression.

3. Expressions

expression := 
   ...
   | table-query-expr

3.1 Table query expressions

table-query-expr := 
   from query-source [query-join-type query-join-source] 
      [query-select] [query-group-by] [query-order-by]
      [query-having] [query-limit]
query-source := identifier [as identifier] [query-where]
query-where := where expression
query-join-type := [([left | right | full] outer)| inner] join
query-join-source := query-source on expression
query-select := select (* | query-select-list)
query-select-list := 
   expression [as identifier] (, expression [as identifier])*
query-group-by := group by identifier (, identifier)*
query-order-by :=
   order by identifier [(ascending | descending)]
      (, identifier [(ascending | descending)])*
query-having := having expression
query-limit := limit int-literal

Query expressions bring language integrated SQL-like querying to Ballerina tables.

4. Actions and statements

statement := 
   ...
   | lock-stmt
   | forever-stmt
   | transaction-stmt
   | transaction-control-stmt

4.1 Lock statement

lock-stmt := lock block-stmt

A lock statement is used to execute a series of assignment statements in a serialized manner. For each variable that is used as an L-value within the block statement, this statement attempts to first acquire a lock and the entire statement executes only after acquiring all the locks. If a lock acquisition fails after some have already been acquired then all acquired locks are released and the process starts again.

4.2 Forever statement

forever-stmt :=
   forever { 
      streaming-query-pattern+
   }
streaming-query-pattern :=
   streaming-query-expr => ( array-type-descriptor identifier )
      block-stmt
streaming-query-expr :=
   from (sq-source [query-join-type sq-join-source]) | sq-pattern
      [query-select] [query-group-by] [query-order-by]
      [query-having] [query-limit] 
      [sq-output-rate-limiting]
sq-source := 
   identifier [query-where] [sq-window [query-where]] 
      [as identifier]*
sq-window := window function-call-expr
sq-join-source := sq-source on expression
sq-output-rate-limiting := 
   sq-time-or-event-output | sq-snapshot-output
sq-time-or-event-output := 
   (all | last | first) every int-literal (time-scale | events)
sq-snapshot-output :=
   snapshot every int-literal time-scale
time-scale := seconds | minutes | hours | days | months | years
sq-pattern := [every] sp-input [sp-within-clause]
sp-within-clause := within expression
sp-input :=
   sp-edge-input ((followed by) | ,) sp-input
   | not sp-edge-input (and sp-edge-input) | (for int-literal time-scale)
   | [sp-edge-input ( and | or ) ] sp-edge-input
   | ( sp-input )
sp-edge-input :=
   identifier [query-where] [sp-int-range-expr] [as identifier]
sp-int-range-expr :=
   [ expression .. [expression] ]

The forever statement is used to execute a set of streaming queries against some number of streams concurrently and to execute a block of code when a pattern matches. The statement will never complete and therefore the worker containing it will never complete.

4.3 Transaction statement

transaction-stmt := transaction trans-conf? block-stmt trans-retry?
transaction-control-stmt := retry-stmt | abort-stmt
trans-conf := trans-conf-item (, trans-conf-item)*
trans-conf-item := trans-retries | trans-oncommit | trans-onabort
trans-retries := retries = expression
trans-oncommit := oncommit = identifier
trans-onabort := onabort = identifier
trans-retry := onretry block-stmt
retry-stmt := retry ;
abort-stmt := abort ;

A transaction statement is used to execute a block of code within a 2PC transaction. A transaction can be established by this statement or it may inherit one from the current worker.

4.3.1 Initiated transactions

If no transaction context is present in the worker then the transaction statement starts a new transaction (i.e., becomes the initiator) and executes the statements within the transaction statement.

Upon completion of the block the transaction is immediately tried to be committed. If the commit succeeds, then if there's an on-commit handler registered that function gets invoked to signal that the commit succeeded. If the commit fails, and if the transaction has not been retried more times than the value of the retries configuration, then the on-retry block is executed and the transaction block statement will execute again in its entirety. If there are no more retries available then the commit is aborted the on-abort function is called.

The transaction can also be explicitly aborted using an abort statement, which will call the on-abort function and give up the transaction (without retrying).

If a retry statement is executed if the transaction has not been retried more times than the value of the retries configuration, then the on-retry block is executed and the transaction block statement will execute again in its entirety.

4.3.2 Participated transactions

If a transaction context is present in the executing worker context, then the transaction statement joins that transaction and becomes a participant of that existing transaction. In this case, retries will not occur as the transaction is under the control of the initiator. Further, if the transaction is locally aborted (by using the abort statement), the transaction gets marked for abort and the participant will fail the transaction when it is asked to prepare for commit by the coordinator of the initiator. When the initiating coordinator decides to abort the transaction it will notify all the participants globally and their on-abort functions will be invoked. If the initiating coordinator decides to retry the transaction then a new transaction is created and the process starts with the entire containing executable entity (i.e. resource or function) being re-invoked with the new transaction context.

When the transaction statement reaches the end of the block the transaction is marked as ready to commit. The actual commit will happen when the coordinator sends a commit message to the participant and after the commit occurs the on-commit function will be invoked. Thus, reaching the end of the transaction statement and going past does not have the semantic of the transaction being committed nor of it being aborted. Thus, if statements that follow the transaction statement they are unaware whether the transaction has committed or aborted.

When in a participating transaction, a retry statement is a no-op.

4.3.3 Transaction propagation

The transaction context in a worker is always visible to invoked functions. Thus any function invoked within a transaction, which has a transaction statement within it, will behave according to the "participated transactions" semantics above.

The transaction context is also propagated over the network via the Ballerina Microtransaction Protocol [XXX].

5. Lang library

The stream basic type has a lang library module lang.stream.