Relations
Relations are tables — they hold rows of typed data. Every relation starts with a .decl and can be annotated with .input, .output, or .printsize to control how data flows in and out.
Declaring a relation
Use .decl to define a relation's schema — its name and the type of each column:
.decl Edge(src: int32, dst: int32)
.decl Person(id: int64, name: string, active: bool)
Every column needs an explicit type. No column can be left untyped.
I/O annotations
.input, .output, and .printsize are annotations — they're not mutually exclusive. Stick any combination on a relation:
| Annotation | What it does |
|---|---|
.input | Load facts from a CSV file (or the interactive shell in incremental mode) |
.output | Write the relation's final contents to a file (or stderr) |
.printsize | Print the tuple count after execution |
The key idea: a relation with .input can also appear in rule heads and carry .output. You just annotate what goes in and what comes out — FlowLog doesn't force an "input-only vs computed-only" split.
.decl Reach(x: int32, y: int32)
.input Reach(IO="file", filename="Reach.csv", delimiter=",")
// Extend loaded seed pairs with transitive closure
Reach(x, z) :- Reach(x, y), Reach(y, z).
.output Reach
Here Reach loads seed pairs from CSV, a rule derives the transitive closure on top, and .output writes the full result.
Inline facts
You can write facts directly in the source file:
.decl Edge(src: int32, dst: int32)
Edge(1, 2).
Edge(2, 3).
Edge(3, 4).
These get merged with any file-loaded data. Handy for small seed sets or test fixtures.
.input in detail
.decl Arc(x: int32, y: int32)
.input Arc(IO="file", filename="Arc.csv", delimiter=",")
| Parameter | Description | Example |
|---|---|---|
IO | "file" for CSV, "cmd" for interactive shell | IO="file" |
filename | Path to the CSV file | filename="Arc.csv" |
delimiter | Column separator | delimiter="," |
The compiler flag -F <DIR> sets the base directory for filename. So -F data + filename="Arc.csv" reads data/Arc.csv.
Use IO="cmd" for incremental mode — inputs come from the interactive shell instead of files.
.output in detail
.decl Reach(id: int32)
.output Reach
The compiler flag -D <DIR> controls where output CSVs land. Pass -D - to print tuples to stderr instead — useful for quick debugging.
.printsize in detail
.decl Reach(id: int32)
.printsize Reach
Prints the number of tuples after execution. You can combine it with .output on the same relation — get both the count and the data.
Putting it all together
.decl Source(id: int32)
.input Source(IO="file", filename="Source.csv", delimiter=",")
.decl Arc(x: int32, y: int32)
.input Arc(IO="file", filename="Arc.csv", delimiter=",")
.decl Reach(id: int32)
.printsize Reach
Reach(y) :- Source(y).
Reach(y) :- Reach(x), Arc(x, y).
Validation
The compiler catches common mistakes at parse time:
- Using a directive on a relation that hasn't been declared.
- Duplicate
.input,.output, or.printsizeon the same relation.