[/Band|c00t] GettingStarted Specification Download Blog About
Fork me on GitHub

Start Up

$ curl -O http://bandilab.org/download/linux/amd64/bandicoot-v5.tar.gz
$ tar xvfz bandicoot-v5.tar.gz
$ cd bandicoot-v5/examples
$ mkdir volume
$ ./bandicoot start -p 12345 -c program.b -d volume -s tx.state

To stop Bandicoot simply kill it or press CTRL-C in the terminal. All in-progress transactions will be rolled back automatically.

 Programs

Bandicoot programs are defined in text files similar to other programming languages (e.g. C, Java). Conventional file suffix is .b. The programs are written in Bandicoot language and they define types, variables, and functions. Here is an example which defines type Books and function Echo:

type Books {
   title string,
   genre string,
   price real,
   pages int
}

fn Echo(b Books) Books
{
    return b;
}

 API

Every function is automatically exposed via HTTP. For example the Echo function declared above will have the following URL: http://localhost:12345/Echo. If a function takes a relational parameter, it needs to be called with the HTTP POST method. Otherwise, the HTTP GET method is required.

Bandicoot uses a CSV (Comma Separated Values) format to exchange the data. The first line defines the attributes of a relation and the remaining lines contain values (tuples).

$ cat one.csv
title string,pages int,price real,genre string
Robinson Crusoe,312,11.21,Novel

$ curl --data-binary @one.csv http://localhost:12345/Echo
genre string,pages int,price real,title string
Novel,312,11.21,Robinson Crusoe

N.B.: examples use the curl command line utility from a terminal window (–data-binary @file is the syntax for submitting a file via HTTP POST). Obviously, you can use any other tools and libraries which support HTTP/1.1 (unfortunately, the wget tool supports only HTTP/1.0 and hence it does not work with Bandicoot).

 No Duplicates, No Order

The Echo function removes duplicate tuples. The reason is due to the nature of Relation. Relations are mathematical sets of tuples and do not allow duplicates. Moreover, keep in mind that sets do not have order and hence the tuples can be returned in a different order:

$ cat duplicate.csv
title string,pages int,price real,genre string
Oliver Twist,608,12.99,Fiction
Robinson Crusoe,312,11.21,Novel
Robinson Crusoe,312,11.21,Novel

$ curl --data-binary @duplicate.csv http://localhost:12345/Echo
genre string,pages int,price real,title string
Novel,312,11.21,Robinson Crusoe
Fiction,608,12.99,Oliver Twist

 Data Manipulation

Bandicoot defines 8 relational operators: rename, project, extend, select, union, join, minus, and summary (see the specification page or a relational algebra diagram for details).

The operators take one or more relations as input, perform a specific action, and return a new relation as output. This principle allows chaining the operators in a single statement to perform multiple actions.

Functions are the primary means of accessing and changing data in Bandicoot. If you do complex calculations and you wish to reuse the results within a function, you can use temporary variables. The type of a temporary variable is determined from the value assigned to it and the value is immutable.

# selects fiction books from the input
fn Fiction(b Books) Books
{
    return select genre == "Fiction" b;
}

# calculates an average price of fiction books
fn FictionPrice(b Books) {avgPrice real}
{
    # use of a temporary variable and a chained statement
    var res = summary avgPrice = (avg price 0.0)
                      (select genre == "Fiction" b);

    return res;
}

Test:

$ cat books.csv
title string,pages int,price real,genre string
The Iliad,576,26.40,Poetry
The Odyssey,416,19.95,Poetry
Oliver Twist,608,12.99,Fiction

$ curl --data-binary @books.csv http://localhost:12345/Fiction
genre string,pages int,price real,title string
Fiction,608,12.99,Oliver Twist

$ curl --data-binary @books.csv http://localhost:12345/FictionPrice
avgPrice real
12.99

 Data Persistence

Bandicoot provides data persistence automatically and transparently by using global variables. They keep the data among different invocations of functions and Bandicoot restarts. The variables are saved to a directory specified as -d parameter during start up.

The global variables are always modified within a transaction. If a function modifies one ore more global variables the new content becomes available after the function executes successfully. In case of an error the changes are rolled back immediately.

var books Books;

fn Write(b Books) void
{
    books = b;
}

fn Titles() {title string}
{
    return books;
}

Test:

$ curl --data-binary @books.csv http://localhost:12345/Write

$ curl http://localhost:12345/Titles
title string
The Iliad
The Odyssey
Oliver Twist

N.B: there are some limitations while working with global variables:

 Insert, Update, and Delete

Bandicoot has one approach for changing the data: assignment. This means that there is no separate insert, update or delete operator and all these actions are implemented via assignment.

Adding of new tuples to an existing set is achieved with the help of the union operator (+ symbol). The union merges two sets of tuples together and removes any duplicate entries.

Updates are implemented with the help of the extend operator. Once the data set has been extended, you need to replace the existing attribute with a new one.

In case of a delete there are several options how to remove the desired set of tuples from relational variable. The DeleteFiction function defined below removes all the tuples which do not match to the given boolean expression.

fn AddBooks(b Books) void
{
    books = books + b;
}

fn UpdatePrice() void
{
    books = rename price = reduced
                   (project title, genre, pages, reduced
                            (extend reduced = price * 0.90 books));
}

fn DeleteFiction() void
{
    books = select genre != "Fiction" books;
}

Insert test:

$ curl http://localhost:12345/Titles
title string
The Iliad
The Odyssey
Oliver Twist

$ cat more-books.csv
title string,pages int,price real,genre string
Oliver Twist,608,12.99,Fiction
Grimm's Fairy Stories,560,14.50,Fairy tales

$ curl --data-binary @more-books.csv http://localhost:12345/AddBooks

$ curl http://localhost:12345/Titles
title string
The Iliad
The Odyssey
Oliver Twist
Grimm's Fairy Stories

Update test:

$ curl http://localhost:12345/Prices
price real,title string
26.4,The Iliad
19.95,The Odyssey
12.99,Oliver Twist
14.5,Grimm's Fairy Stories

$ curl http://localhost:12345/UpdatePrice

$ curl http://localhost:12345/Prices
price real,title string
23.76,The Iliad
17.955,The Odyssey
11.691,Oliver Twist
13.05,Grimm's Fairy Stories

Delete test:

$ curl http://localhost:12345/Titles
title string
The Iliad
The Odyssey
Oliver Twist
Grimm's Fairy Stories

$ curl http://localhost:12345/DeleteFiction

$ curl http://localhost:12345/Titles
title string
The Iliad
The Odyssey
Grimm's Fairy Stories

Webapps

Connect your browser directly to the Bandicoot. To call a Bandicoot function from the JavaScript you can use the XMLHttpRequest object available in most browsers. Bandicoot supports the Cross-Origin Resource Sharing (CORS) specification which makes it easy to develop your web applications on a local computer.

Here is a small JavaScript example which connects to the Bandicoot:

var csv = "x int\n1\n2\n1\n2\n3";

var req = new XMLHttpRequest();
req.open("POST", "http://localhost:12345/Unique", false);
req.send(csv);

if (req.status == 200)
    confirm(req.responseText);
else
    throw "status: " + req.status;

relation.js

For a more sophisticated program you will need some tool which can parse the Bandicoot output (CSV). The parser is called relation.js and provides the following functionality:

relation.js source code (public domain):

function Rel(csv) {
    this.lines = split(csv, "\n");
    this.head = new Head(this.lines[0]);
    this.pos = 1;

    // if returnArray is undefined returns an assoc. array attrName->value
    // otherwise returns tuple values as array of strings
    this.next = function(returnArray) {
        if (this.pos >= this.lines.length || this.lines[this.pos] === "")
            return null;

        var attrs = split(this.lines[this.pos], ",");
        if (attrs.length != this.head.len)
            throw "bad tuple [" + this.lines[this.pos] + "]";

        this.pos++;

        var res = attrs;
        if (returnArray === undefined) {
            res = [];
            for (var i = 0; i < attrs.length; ++i)
                res[this.head.names[i]] = attrs[i];
        }

        return res;
    };

    this.reset = function() {
        this.pos = 1;
    };

    this.count = function() {
        return this.lines.length - 1;
    };
}

function Head(line) {
    var namesTypes = split(line, ",");

    this.len = namesTypes.length;
    this.attrs = [];
    this.names = [];

    for (var i = 0; i < this.len; ++i) {
        var nt = split(namesTypes[i], " ");
        if (nt.length != 2)
            throw "bad head attribute [" + namesTypes[i] + "]";

        this.attrs[nt[0]] = nt[1];
        this.names[i] = nt[0];
    }
}

function split(data, delim) {
    var res = [], line = [];
    var esc = "\\", prev = " ";
    var len = data.length;
    var parts = 0, idx = 0;

    for (var i = 0; i < len; ++i) {
        var c = data.charAt(i);

        if (c != delim || prev == esc)
            line[idx++] = c;

        if ((c == delim && (prev != esc)) || (i + 1) == len) {
            res[parts++] = line.join("");
            line = [];
            idx = 0;
        }

        prev = (prev == esc) ? " " : c;
    }

    return res;
}

For a complete example of a webapp see total.html and total.b in the examples directory. The program dynamically constructs a request from the input fields, then calls the Total function, and finally parses the output with the relation.js displaying the results.