Rules and recursion

Derived relations, and a rule that follows imports all the way down, through a cycle.

A rule defines a new relation from the ones you have. It reads right to left: the part after :- is a goal, and for each way it matches, the head before :- holds. This one says that a imports b within a team when a imports b and the same team owns both:

same_team(?a, ?b) :- imports(?a, ?b), owner(?a, ?t), owner(?b, ?t);
same_team(?a, ?b) => ?a, ?b
?a?b
appapi
logutil
metricsutil
Facts: the deps dataset

A rule ends with ;, and a query is any number of rules followed by one goal. The relation a rule defines can be used in a goal, or in another rule, exactly like one the facts provide. jaala doesn’t know same_team until a rule says what it is, so a query that names a relation with no rules and no facts behind it is refused:

depends_on("app", ?p) => ?p

query: unknown relation "depends_on"

Facts: the deps dataset

Recursion

imports only says what a package imports directly. What a package depends on is what it imports, plus what those import, and so on down. That’s two rules, and the second one uses the relation it defines:

depends_on(?a, ?b) :- imports(?a, ?b);
depends_on(?a, ?c) :- depends_on(?a, ?b), imports(?b, ?c);
depends_on("app", ?p) => ?p
?p
api
auth
cache
config
db
log
util
Facts: the deps dataset

The first rule covers the direct imports. The second says that if a depends on b, and b imports c, then a depends on c too. jaala applies both rules until a round derives nothing new, which is the fixpoint, so it doesn’t really matter how deep the chain goes.

Cycles

cache and config import each other, which would send a naive walk of the graph round in circles. A rule can’t, because jaala only adds facts it didn’t have, and there are finitely many. So cache depends on config, and through it on itself:

depends_on(?a, ?b) :- imports(?a, ?b);
depends_on(?a, ?c) :- depends_on(?a, ?b), imports(?b, ?c);
depends_on("cache", ?p) => ?p
?p
cache
config
log
util
Facts: the deps dataset

Asking why

A goal with no variables asks whether something holds. When it does, the answer is one row with no columns, and its citations say why. app depends on util because app imports log, and log imports util:

depends_on(?a, ?b) :- imports(?a, ?b);
depends_on(?a, ?c) :- depends_on(?a, ?b), imports(?b, ?c);
depends_on("app", "util")
cites
imports("app", "log"), imports("log", "util")
Facts: the deps dataset

app reaches util along several paths, and a row cites one of them, the derivation that found it first. That’s mostly what you want from evidence: one path you can check, not every path there is.

The other direction

The same rules answer the opposite question, too. Put the constant in the second position and you get everything that depends on util, which here is every package but util itself:

depends_on(?a, ?b) :- imports(?a, ?b);
depends_on(?a, ?c) :- depends_on(?a, ?b), imports(?b, ?c);
depends_on(?p, "util") => ?p
?p
api
app
auth
cache
config
db
legacy
log
metrics
Facts: the deps dataset

You don’t write the rules differently for this. jaala works out which arguments a goal fixes, and uses that to avoid deriving what the question doesn’t need.