2023-12-14 17:59:22 +03:00
|
|
|
---
|
|
|
|
language: Ballerina
|
|
|
|
contributors:
|
|
|
|
- ["Anjana Fernando", "https://github.com/lafernando"]
|
|
|
|
filename: learn_ballerina.bal
|
|
|
|
---
|
|
|
|
|
|
|
|
[Ballerina](https://ballerina.io/) is a statically-typed programming language for making development for the cloud an enjoyable experience.
|
|
|
|
|
2024-01-13 22:37:08 +03:00
|
|
|
```java
|
2023-12-14 17:59:22 +03:00
|
|
|
// Single-line comment
|
|
|
|
|
|
|
|
// Import modules into the current source file
|
|
|
|
import ballerina/io;
|
|
|
|
import ballerina/time;
|
|
|
|
import ballerina/http;
|
|
|
|
import ballerinax/java.jdbc;
|
|
|
|
import ballerina/lang.'int as ints;
|
|
|
|
import ballerinax/awslambda;
|
|
|
|
// Module alias "af" used in code in place of the full module name
|
|
|
|
import ballerinax/azure.functions as af;
|
|
|
|
|
|
|
|
http:Client clientEP = new ("https://freegeoip.app/");
|
|
|
|
jdbc:Client accountsDB = new ({url: "jdbc:mysql://localhost:3306/AccountsDB",
|
|
|
|
username: "test", password: "test"});
|
|
|
|
|
|
|
|
// A service is a first-class concept in Ballerina, and is one of the
|
|
|
|
// entrypoints to a Ballerina program.
|
|
|
|
// The Ballerina platform also provides support for easy deployment to
|
|
|
|
// environments such as Kubernetes (https://ballerina.io/learn/deployment/kubernetes/).
|
|
|
|
service geoservice on new http:Listener(8080) {
|
|
|
|
|
|
|
|
@http:ResourceConfig {
|
|
|
|
path: "/geoip/{ip}"
|
|
|
|
}
|
|
|
|
resource function geoip(http:Caller caller, http:Request request,
|
|
|
|
string ip) returns @tainted error? {
|
|
|
|
http:Response resp = check clientEP->get("/json/" + <@untainted>ip);
|
|
|
|
check caller->respond(<@untainted> check resp.getTextPayload());
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serverless Function-as-a-Service support with AWS Lambda.
|
|
|
|
// The Ballerina compiler automatically generates the final deployment
|
|
|
|
// artifact to be deployed.
|
|
|
|
@awslambda:Function
|
|
|
|
public function echo(awslambda:Context ctx, json input) returns json {
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
|
|
|
|
@awslambda:Function
|
|
|
|
public function notifyS3(awslambda:Context ctx,
|
|
|
|
awslambda:S3Event event) returns json {
|
|
|
|
return event.Records[0].s3.'object.key;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serverless Function-as-a-Service support with Azure Functions.
|
|
|
|
// Similar to AWS Lambda, the compiler generates the deployment artifacts.
|
|
|
|
@af:Function
|
|
|
|
public function fromQueueToQueue(af:Context ctx,
|
|
|
|
@af:QueueTrigger { queueName: "queue1" } string inMsg,
|
|
|
|
@af:QueueOutput { queueName: "queue2" } af:StringOutputBinding outMsg) {
|
|
|
|
outMsg.value = inMsg;
|
|
|
|
}
|
|
|
|
|
|
|
|
// A custom record type
|
|
|
|
public type Person record {
|
|
|
|
string id; // required field
|
|
|
|
string name;
|
|
|
|
int age?; // optional field
|
|
|
|
string country = "N/A"; // default value
|
|
|
|
};
|
|
|
|
|
|
|
|
@af:Function
|
|
|
|
public function fromHttpTriggerCosmosDBInput(
|
|
|
|
@af:HTTPTrigger { route: "c1/{country}" } af:HTTPRequest httpReq,
|
|
|
|
@af:CosmosDBInput { connectionStringSetting: "CosmosDBConnection",
|
|
|
|
databaseName: "db1", collectionName: "c1",
|
|
|
|
sqlQuery: "select * from c1 where c1.country = {country}" }
|
|
|
|
Person[] dbReq)
|
|
|
|
returns @af:HTTPOutput string|error {
|
|
|
|
return dbReq.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function main() returns @tainted error? {
|
|
|
|
int a = 10; // 64-bit signed integer
|
|
|
|
float b = 1.56; // 64-bit IEEE 754-2008 binary floating point number
|
|
|
|
string c = "hello"; // a unicode string
|
|
|
|
boolean d = true; // true, false
|
|
|
|
decimal e = 15.335; // decimal floating point number
|
|
|
|
|
|
|
|
var f = 20; // type inference with 'var' - 'f' is an int
|
|
|
|
|
|
|
|
int[] intArray = [1, 2, 3, 4, 5, 6];
|
|
|
|
int x = intArray.shift(); // similar to a dequeue operation
|
|
|
|
x = intArray.pop(); // removes the last element
|
|
|
|
intArray.push(10); // add to the end
|
|
|
|
|
|
|
|
// Tuples - similar to a fixed length array with a distinct type for each slot
|
|
|
|
[string, int] p1 = ["Jack", 1990];
|
|
|
|
[string, int] p2 = ["Tom", 1986];
|
|
|
|
io:println("Name: ", p1[0], " Birth Year: ", p1[1]);
|
|
|
|
|
|
|
|
string name1;
|
|
|
|
int birthYear1;
|
|
|
|
[name1, birthYear1] = p1; // tuple destructuring
|
|
|
|
|
|
|
|
var [name2, birthYear2] = p2; // declare and assign values in the same statement
|
|
|
|
|
|
|
|
// If statements
|
|
|
|
int ix = 10;
|
|
|
|
if ix < 10 {
|
|
|
|
io:println("value is less than 10");
|
|
|
|
} else if ix == 10 {
|
|
|
|
io:println("value equals to 10");
|
|
|
|
} else {
|
|
|
|
io:println("value is greater than 10");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loops
|
|
|
|
int count = 10;
|
|
|
|
int i = 0;
|
|
|
|
while i < 10 {
|
|
|
|
io:println(i);
|
|
|
|
}
|
|
|
|
// Loop from 0 to count (inclusive)
|
|
|
|
foreach var j in 0...count {
|
|
|
|
io:println(j);
|
|
|
|
}
|
|
|
|
// Loop from 0 to count (non-inclusive)
|
|
|
|
foreach var j in 0..<count {
|
|
|
|
io:println(j);
|
|
|
|
}
|
|
|
|
// Loop a list
|
|
|
|
foreach var j in intArray {
|
|
|
|
io:println(j);
|
|
|
|
}
|
|
|
|
|
|
|
|
json j1 = { "name" : name1, "birthYear" : birthYear1, "zipcode" : 90210 };
|
|
|
|
io:println(j1.name, " - ", j1.zipcode);
|
|
|
|
// New fields are added to a JSON value through "mergeJson"
|
|
|
|
var j2 = j1.mergeJson({ "id" : "90400593053"});
|
|
|
|
|
|
|
|
// XML namespace declaration
|
|
|
|
xmlns "http://example.com/ns1" as ns1;
|
|
|
|
xmlns "http://example.com/default";
|
|
|
|
|
|
|
|
// XML variable from a literal value
|
|
|
|
xml x1 = xml `<ns1:entry><name>{{name1}}</name><birthYear>{{birthYear1}}</birthYear></ns1:entry>`;
|
|
|
|
io:println(x1);
|
|
|
|
// Access specific elements in the XML value
|
|
|
|
io:println(x1/<name>);
|
|
|
|
// List all child items in the XML value
|
|
|
|
io:println(x1/*);
|
|
|
|
|
|
|
|
// Function invocations
|
|
|
|
x = add(1, 2);
|
|
|
|
io:println(multiply(2, 4));
|
|
|
|
// Invocation providing value for the defaultable parameter
|
|
|
|
io:println(multiply(3, 4, true));
|
|
|
|
// Invocation with values to a rest parameter (multi-valued)
|
|
|
|
io:println(addAll(1, 2, 3));
|
|
|
|
io:println(addAll(1, 2, 3, 4, 5));
|
|
|
|
|
|
|
|
// Function pointers
|
|
|
|
(function (int, int) returns int) op1 = getOperation("add");
|
|
|
|
(function (int, int) returns int) op2 = getOperation("mod");
|
|
|
|
io:println(op1(5, 10));
|
|
|
|
io:println(op2(13, 10));
|
|
|
|
|
|
|
|
// Closures
|
|
|
|
(function (int x) returns int) add5 = getAdder(5);
|
|
|
|
(function (int x) returns int) add10 = getAdder(10);
|
|
|
|
io:println(add5(10));
|
|
|
|
io:println(add10(10));
|
|
|
|
|
|
|
|
int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
|
|
// Functional iteration
|
|
|
|
int[] evenNumbers = numbers.filter(function (int x) returns boolean { return x % 2 == 0; });
|
|
|
|
|
|
|
|
// Union types - "input" is of type either string or byte[]
|
|
|
|
string|byte[] uval = "XXX";
|
|
|
|
|
|
|
|
// A type test expression ("uval is string") can be used to check the
|
|
|
|
// runtime type of a variable.
|
|
|
|
if uval is string {
|
|
|
|
// In the current scope, "uval" is a string value
|
|
|
|
string data = "data:" + uval;
|
|
|
|
} else {
|
|
|
|
// Since the expression in the "if" statement ruled out that it's not a string,
|
|
|
|
// the only type left is "byte[]"; so in the current scope, "uval" will always
|
|
|
|
// be a "byte[]".
|
|
|
|
int inputLength = uval.length();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error handling
|
|
|
|
string input = io:readln("Enter number: ");
|
|
|
|
int|error result = ints:fromString(input);
|
|
|
|
if result is int {
|
|
|
|
io:println("Number: ", result);
|
|
|
|
} else {
|
|
|
|
io:println("Invalid number: ", input);
|
|
|
|
}
|
|
|
|
|
|
|
|
// A check expression can be used to directly return the error from
|
|
|
|
// the current function if its subexpression evaluated to an error
|
|
|
|
// value in the runtime.
|
|
|
|
int number = check ints:fromString(input);
|
|
|
|
|
|
|
|
// Concurrent execution using workers in a function
|
|
|
|
doWorkers();
|
|
|
|
|
|
|
|
// Asynchronous execution with futures
|
|
|
|
future<int> f10 = start fib(10);
|
|
|
|
var webresult = clientEP->get("/");
|
|
|
|
int fresult = wait f10;
|
|
|
|
if webresult is http:Response {
|
|
|
|
io:println(webresult.getTextPayload());
|
|
|
|
io:println(fresult);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mapping types
|
|
|
|
map<int> ageMap = {};
|
|
|
|
ageMap["Peter"] = 25;
|
|
|
|
ageMap["John"] = 30;
|
|
|
|
|
|
|
|
int? agePeter = ageMap["Peter"]; // int? is the union type int|() - int or nill
|
|
|
|
if agePeter is int {
|
|
|
|
io:println("Peter's age is ", agePeter);
|
|
|
|
} else {
|
|
|
|
io:println("Peter's age is not found");
|
|
|
|
}
|
|
|
|
|
|
|
|
Person person1 = { id: "p1", name : "Anne", age: 28, country: "Sri Lanka" };
|
|
|
|
Scores score1 = { physics : 80, mathematics: 95 };
|
|
|
|
score1["chemistry"] = 75;
|
|
|
|
io:println(score1["chemistry"]);
|
|
|
|
|
|
|
|
Student student1 = { id: "s1", name: "Jack", age: 25, country: "Japan" };
|
|
|
|
student1.college = "Stanford";
|
|
|
|
string? jacksCollege = student1?.college; // optional field access
|
|
|
|
if jacksCollege is string {
|
|
|
|
io:println("Jack's college is ", jacksCollege);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Due to the structural type system, "student1" can be assigned to "person2",
|
|
|
|
// since the student1's structure is compatible with person2's,
|
|
|
|
// where we can say, a "Student" is a "Person" as well.
|
|
|
|
Person person2 = student1;
|
|
|
|
|
|
|
|
map<int> grades = {"Jack": 95, "Anne": 90, "John": 80, "Bill": 55};
|
|
|
|
Person px1 = {id: "px1", name: "Jack", age: 30, country: "Canada"};
|
|
|
|
Person px2 = {id: "px2", name: "John", age: 25};
|
|
|
|
Person px3 = {id: "px3", name: "Anne", age: 17, country: "UK"};
|
|
|
|
Person px4 = {id: "px4", name: "Bill", age: 15, country: "USA"};
|
|
|
|
Person[] persons = [];
|
|
|
|
persons.push(px1);
|
|
|
|
persons.push(px2);
|
|
|
|
persons.push(px3);
|
|
|
|
persons.push(px4);
|
|
|
|
|
|
|
|
// Query expressions used to execute complex queries for list data
|
|
|
|
Result[] results = from var person in persons
|
|
|
|
let int lgrade = (grades[person.name] ?: 0)
|
|
|
|
where lgrade > 75
|
|
|
|
let string targetCollege = "Stanford"
|
|
|
|
select {
|
|
|
|
name: person.name,
|
|
|
|
college: targetCollege,
|
|
|
|
grade: lgrade
|
|
|
|
};
|
|
|
|
|
|
|
|
// Compile-time taint checking for handling untrusted data
|
|
|
|
string s1 = "abc";
|
|
|
|
mySecureFunction(s1);
|
|
|
|
// Explicitely make "s2" a tainted value. External input to a Ballerina
|
|
|
|
// program such as command-line arguments and network input are by-default
|
|
|
|
// marked as tainted data.
|
|
|
|
string s2 = <@tainted> s1;
|
|
|
|
// "s2x" is now a tainted value, since its value is derived using a
|
|
|
|
// tainted value (s1).
|
|
|
|
string s2x = s2 + "abc";
|
|
|
|
// The following line uncommented will result in a compilation error,
|
|
|
|
// since we are passing a tainted value (s2x) to a function which
|
|
|
|
// exepects an untainted value.
|
|
|
|
// mySecureFunction(s2x);
|
|
|
|
|
|
|
|
// Instantiating objects
|
|
|
|
Employee emp1 = new("E0001", "Jack Smith", "Sales", 2009);
|
|
|
|
io:println("The company service duration of ", emp1.name,
|
|
|
|
" is ", emp1.serviceDuration());
|
|
|
|
|
|
|
|
// Supported operations can be executed in a transaction by enclosing the actions
|
|
|
|
// in a "transaction" block.
|
|
|
|
transaction {
|
|
|
|
// Executes the below database operations in a single local transactions
|
|
|
|
var r1 = accountsDB->update("UPDATE Employee SET balance = balance + ? WHERE id = ?", 5500.0, "ID001");
|
|
|
|
var r2 = accountsDB->update("UPDATE Employee SET balance = balance + ? WHERE id = ?", 5500.0, "ID001");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// An object is a behavioural type, which encapsulates both data and functionality.
|
|
|
|
type Employee object {
|
|
|
|
|
|
|
|
// Private fields are only visible within the object and its methods
|
|
|
|
private string empId;
|
|
|
|
// Public fields can be accessed by anyone
|
|
|
|
public string name;
|
|
|
|
public string department;
|
|
|
|
// The default qualifier is a "protected" field,
|
|
|
|
// which are accessible only within the module.
|
|
|
|
int yearJoined;
|
|
|
|
|
|
|
|
// The object initialization function; automatically called when an object is instantiated.
|
|
|
|
public function __init(string empId, string name, string department, int yearJoined) {
|
|
|
|
self.empId = empId;
|
|
|
|
self.name = name;
|
|
|
|
self.department = department;
|
|
|
|
self.yearJoined = yearJoined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// An object method
|
|
|
|
public function serviceDuration() returns int {
|
|
|
|
time:Time ct = time:currentTime();
|
|
|
|
return time:getYear(ct) - self.yearJoined;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// Student is a subtype of Person
|
|
|
|
type Student record {
|
|
|
|
string id;
|
|
|
|
string name;
|
|
|
|
int age;
|
|
|
|
string college?;
|
|
|
|
string country;
|
|
|
|
};
|
|
|
|
|
|
|
|
type Scores record {
|
|
|
|
int physics;
|
|
|
|
int mathematics;
|
|
|
|
};
|
|
|
|
|
|
|
|
type Result record {
|
|
|
|
string name;
|
|
|
|
string college;
|
|
|
|
int grade;
|
|
|
|
};
|
|
|
|
|
|
|
|
public function getOperation(string op) returns (function (int, int) returns int) {
|
|
|
|
if op == "add" {
|
|
|
|
return add;
|
|
|
|
} else if op == "mod" {
|
|
|
|
return function (int a, int b) returns int { // anonymous function
|
|
|
|
return a % b;
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return (x, y) => 0; // single expression anonymous no-op function
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Two required parameters
|
|
|
|
public function add(int a, int b) returns int {
|
|
|
|
return a + b;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 'log' is a defaultable parameter
|
|
|
|
public function multiply(int a, int b, boolean log = false) returns int {
|
|
|
|
if log {
|
|
|
|
io:println("Multiplying ", a, " with ", b);
|
|
|
|
}
|
|
|
|
return a * b;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 'numbers' is a rest parameter - it can have multiple values,
|
|
|
|
// similar to an array.
|
|
|
|
public function addAll(int... numbers) returns int {
|
|
|
|
int result = 0;
|
|
|
|
foreach int number in numbers {
|
|
|
|
result += number;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAdder(int n) returns (function (int x) returns int) {
|
|
|
|
return function (int x) returns int { // returns closure
|
|
|
|
return x + n;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function fib(int n) returns int {
|
|
|
|
if n <= 2 {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return fib(n - 1) + fib(n - 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The code in worker blocks "w1" and "w2" are executed concurrency
|
|
|
|
// when this function is invoked. The "wait" expressions waits for
|
|
|
|
// the given workers to finish to retrieve their results.
|
|
|
|
public function doWorkers() {
|
|
|
|
worker w1 returns int {
|
|
|
|
int j = 10;
|
|
|
|
j -> w2;
|
|
|
|
int b;
|
|
|
|
b = <- w2;
|
|
|
|
return b * b;
|
|
|
|
}
|
|
|
|
worker w2 returns int {
|
|
|
|
int a;
|
|
|
|
a = <- w1;
|
|
|
|
a * 2 -> w1;
|
|
|
|
return a + 2;
|
|
|
|
}
|
|
|
|
record {int w1; int w2;} x = wait {w1, w2};
|
|
|
|
io:println(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// A function which takes in only an untainted string value.
|
|
|
|
public function mySecureFunction(@untainted string input) {
|
|
|
|
io:println(input);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Further Reading
|
|
|
|
|
|
|
|
* [Ballerina by Example](https://ballerina.io/learn/by-example/)
|
|
|
|
* [User Guide](https://ballerina.io/learn/installing-ballerina/)
|
|
|
|
* [API Documentation](https://ballerina.io/learn/api-docs/ballerina/)
|
|
|
|
* [Language Specification](https://ballerina.io/spec/)
|