Derived query definition and the defquery macro.
A derived query is a pure function from a key to a value that may call other queries. The framework memoizes results and tracks dependencies automatically.
Usage
defmodule MyLang.Queries do
use Roux.Query
definput :source_text, durability: :low
defentity MyLang.Definition
defquery :parse, key: file_path, returns: {:ok, [atom()]} | {:error, term()} do
source = input(db, :source_text, file_path)
MyParser.parse(source)
end
enddefquery generates a public function wrapping the body in
Roux.Runtime.execute/4 for memoization and dependency tracking.
definput records an input definition for bulk registration.
defentity declares an entity type for automatic registration.
Code versions
A memoized value is a function of what its query read and of the
code that computed it. Roux tracks the reads; the code it knows about
through a query's code version, stored with each entry: an entry
computed by another version of its query is stale (Roux.Validation),
and re-executes — keeping its changed_at when it comes back to the
same value, so an edit that changes no result recomputes nothing
downstream.
use Roux.Query, code: [exclude: &MyApp.Schema.schema_module?/1]
defquery :facts, key: module, code: {MyApp.Extractors, :all, []} do
...
end
defquery :render, key: finding, version: 2 do
...
enduse Roux.Query, code: opts versions every query of the module by
the code its module reaches (Roux.Code.digest/2, given opts; true
for none): an edit to any module that code calls moves the version of
every query here, and an edit anywhere else moves none. Split queries
into modules by what they run to keep each closure tight. A query's
own code: adds roots its body reaches by dynamic dispatch — a list
of modules, or {m, f, a} returning one at registration — and turns
code versions on for that query alone when the module's are off.
version: is a term mixed into the version by hand, for a change the
code cannot show (a rule file read at runtime, a format).
Versions are computed when the module is registered
(Roux.Lang.register_module/2), once per VM for each set of roots. A
module compiled in memory has no object code to read: its queries get
a version that no other VM shares, so their entries never outlive the
VM.
Persistence
A manifest (Roux.Lang.Manifest) keeps every entry by default, its
value inline. A query can say otherwise:
defquery :findings, key: analysis, store: :blob do ... end
defquery :facts, key: module, transient: &match?({:error, :lost}, &1) do
...
endstore: :inline(the default) — the value is in the manifest;store: :blob— the value is kept in the manifest'sRoux.Blobstore by digest, read back lazily, and its early cutoff compares digests: for large values the manifest need not carry;store: :none— never kept: cheap to recompute, or meaningless in another VM;transient: predicate— a value the predicate accepts is not kept, and neither is any entry that read it, directly or not: a reader restored without it would pass its durability check and serve what the transient value led to, never asking again. For a value that stands for a failure worth retrying next run.
Wrapping bodies
use Roux.Query, around: {m, f} runs every query body of the module
inside m.f(context, body), where context is
%{db: db, query: name, key: key} and body a zero-arity function
returning the body's value; f returns what the query returns. It
runs inside the query's execution, so what it reads becomes the
query's dependencies: a hook that records what the body read some
other way and turns it into edges.
Definition format
What use Roux.Query generates — the definitions of
__roux_queries__/0 and the query functions' calls into roux — has
a format (format/0), stamped on the module as __roux_format__/0.
Registration reads only its own: a module compiled against another
roux raises Roux.Query.FormatError rather than run code its
runtime does not match. A Mix build recompiles such a module when
roux changes, and Roux.Lang.Compiler waits for that (see there).
Summary
Types
What use Roux.Query, code: ... holds: nil, or the options of Roux.Code.digest/2.
Functions
Checks that module, a loaded module that uses Roux.Query, was
compiled against a roux of this definition format (format/0).
Declares an entity type for automatic registration.
Declares an input with optional durability.
Defines a derived query.
The definition format of this roux (see "Definition format").
Types
@type code_options() :: [Roux.Code.option()] | nil
What use Roux.Query, code: ... holds: nil, or the options of Roux.Code.digest/2.
@type definition() :: Roux.Query.Definition.t()
@type query_name() :: atom()
Functions
@spec check_format(module()) :: :ok | {:error, Roux.Query.FormatError.t()}
Checks that module, a loaded module that uses Roux.Query, was
compiled against a roux of this definition format (format/0).
@spec code_version(Roux.Query.Definition.t(), code_options(), Roux.Blob.t() | nil) :: binary() | nil
The code version of definition (see "Code versions"), given the
code options of its module (use Roux.Query, code: ...): nil for a
query with neither code versions nor a version:. With a Roux.Blob
store, the code's digest is kept there across VMs (Roux.Code).
Declares an entity type for automatic registration.
Accumulates the entity module so that Roux.Lang.register_module/2
registers it with the database alongside queries and inputs.
Example
defentity MyLang.Function
Declares an input with optional durability.
Accumulates a Roux.Input.Definition for bulk registration. Does not
create the input in the database — that happens at module registration time.
Options
:durability—:high,:medium, or:low(default::medium)
Defines a derived query.
Generates a public function name(db, key) that wraps the body in
Roux.Runtime.execute/4 and accumulates a Roux.Query.Definition for
registration.
Options
:key— (required) the key parameter pattern:returns— (optional) return type; generates a@specfor the query function:code— (optional) roots of the query's code beyond its module: a list of modules or{module, function, args}(see "Code versions"):version— (optional) a term mixed into the query's code version:store— (optional):inline,:blobor:none(see "Persistence"):transient— (optional) a predicate over the value (see "Persistence"):do— the query body block
Example
defquery :parse, key: file_path, returns: {:ok, [AST.t()]} | {:error, String.t()} do
source = input(db, :source_text, file_path)
MyParser.parse(source)
end
@spec format() :: pos_integer()
The definition format of this roux (see "Definition format").