Roux.Query (roux v0.2.2)

Copy Markdown View Source

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
end

defquery 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
  ...
end

use 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
  ...
end
  • store: :inline (the default) — the value is in the manifest;
  • store: :blob — the value is kept in the manifest's Roux.Blob store 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).

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.

Declares an input with optional durability.

Defines a derived query.

The definition format of this roux (see "Definition format").

Types

code_options()

@type code_options() :: [Roux.Code.option()] | nil

What use Roux.Query, code: ... holds: nil, or the options of Roux.Code.digest/2.

definition()

@type definition() :: Roux.Query.Definition.t()

query_name()

@type query_name() :: atom()

Functions

check_format(module)

@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).

code_version(definition, module_code, store \\ nil)

@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).

defentity(module)

(macro)

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

definput(name, opts \\ [])

(macro)

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)

defquery(name, opts)

(macro)

defquery(name, opts, do_block)

(macro)

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 @spec for 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, :blob or :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

format()

@spec format() :: pos_integer()

The definition format of this roux (see "Definition format").