Skip to main content
This page takes you from an empty Linux host to two working Sumatra programs against your own Firebolt database: first the smallest possible program (pure set-DML), then the canonical read → compute → write shape that most real programs use.

Prerequisites

  • A Linux x86-64 host. This is the only packaged platform today.
  • A Firebolt database you can write to, plus a service account (client id and secret) and your account name. Firebolt is the first supported backend — and the only one in the current release.
  • A C++ toolchain. Sumatra compiles each program into a small native binary on your host, so it needs g++ (C++17), pkg-config, and the libcurl development headers:
The toolchain is only exercised when something actually compiles. A host that only runs already-compiled stored procedures from its cache never needs g++ installed.

Install

Sumatra ships as a single tarball, sumatra-<version>-linux-amd64.tar.gz. Unpack it and run it in place:
To put sumatra on your PATH, symlink it — do not copy the binary out of the directory:
The runtime/ and exec/ directories must stay next to the real sumatra binary — they hold the headers and the I/O layer that every compiled program is built with. The tool resolves symlinks to find them, which is why a symlink works and a copied binary does not.

Create a DSN file

The shell connects with a DSN file: a plain text file containing one connection string. Create my.dsn:
my.dsn
  • <database> — the Firebolt database to run against. Required: programs and stored procedures live per-database.
  • account_name, client_id, client_secret — your Firebolt account and service-account credentials.
  • engine — optional; omit it to use the system engine.
The file holds a credential, so protect it:

Create a table to work on

Run this in your Firebolt database (SQL workspace or any client) — the same table the programs in Learning by example build on:

Your first program

Sumatra source lives in files, conventionally with a .sumatra extension. Create expire.sumatra:
expire.sumatra
Reading it line by line:
  • -- starts a comment, to the end of the line.
  • BEGIN ... END; brackets the program body. There are no variables here, so no DECLARE section is needed.
  • The UPDATE is plain SQL in Firebolt’s own dialect. A write that is already set-shaped stays set-shaped — Sumatra runs it server-side as-is and adds no machinery around it.
  • COMMIT; ends the transaction. Commits are always explicit in Sumatra — where you place COMMIT defines exactly what becomes durable together.
  • Every statement ends with ;, including the final END;.

Run it

Point the shell at your DSN file and submit the file with @:
Or skip the prompt entirely — the one-shot form runs a single statement and exits with its outcome, which is the form you use from scripts and cron:
Behind the scenes, the shell just did all of this:
  1. Compiled expire.sumatra against the live catalog of the connected database — the database itself is the schema; there are no schema files to maintain.
  2. Generated C++ for the program and built it with g++ in a temporary directory. (The first build in a session also checks that g++, pkg-config, and libcurl headers are present, and fails loudly with an install hint if not.)
  3. Ran the binary, which connected using the same DSN file and executed the transaction.
  4. Discarded the binary. A file submitted with @ is compile-and-run, never stored — stored procedures are the persistent form.
Verify the result in Firebolt: session 1 flipped to expired, session 2 is untouched.

A program with per-row logic

Set-DML is the trivial case. Sumatra earns its keep when the logic is procedural — when each row needs branching, state, or arithmetic that is awkward to express in one SQL statement. Create an orders table and a few rows:
Now a program that classifies every pending order, prioritize.sumatra:
prioritize.sumatra
The new pieces:
  • FOR batch IN (SELECT ...) opens a read block. The parenthesized query is ordinary Firebolt SQL — filter, join, aggregate as you like. batch is a name you choose.
  • ORDER KEY (order_id) declares a strict total order for the read. It is required whenever the read is fetched in chunks, so results are deterministic and a re-run walks rows in the same order.
  • BATCH SIZE 10000 fetches the result in chunks of up to 10,000 rows. Each batch is processed, written, and committed as a unit.
  • FOR rec IN batch LOOP iterates the current batch row by row, in memory. rec’s fields are the columns of your SELECT, and they are assignable.
  • := is Sumatra assignment (plain = is SQL’s comparison/SET, used only inside SQL statements).
  • UPDATE ... SET ROW = rec WHERE order_id = rec.order_id; writes the whole record back — every column except the key it matches on. You never write MERGE or staging tables yourself; the compiler batches the captured row writes and emits one set-based statement per batch.
  • COMMIT; inside the batch loop makes each batch a durable checkpoint as soon as it is written.
Run it and check:

Exit codes

Every program run finishes in exactly one of three ways: The distinction between 2 and 1 is deliberate: 2 means the program anticipated the fault and shut down gracefully with a valid committed prefix; 1 means something needs a human. Scripts and schedulers can branch on it directly.

Where next