Recall.Schema (Recall v0.1.0)

Copy Markdown View Source

A specialized drop-in replacement for use Ecto.Schema, tuned for the Recall (Mnesia) adapter.

Ecto.Schema is built for SQL adapters: a query carries field names, and the adapter resolves each name to a storage position at runtime. Recall stores every Ecto field as a positional Mnesia attribute, so a field's position is fixed the moment the schema is defined — there's nothing to discover at query time. This module bakes that knowledge into dedicated, compiled functions on the schema itself, so querying by a field dispatches straight to a function that already knows its position. This is the only schema layout the adapter understands; there is no runtime-lookup path for a plain Ecto.Schema.

Use it like Ecto.Schema, but name the table with an atom — a Mnesia table is an atom, so that's the source of truth this adapter works in:

defmodule MyApp.User do
  use Recall.Schema

  schema :users do
    field :name, :string
    field :age, :integer
  end
end

Everything Ecto.Schema provides — the struct, __schema__/1, changesets, associations — keeps working untouched; this only adds the compiled accessors the adapter relies on. Defining a schema with this macro is required: the adapter resolves every field through these baked accessors and has no runtime fallback for a plain use Ecto.Schema schema.

Atom table names

Ecto.Schema's schema/2 requires a string source (it's built for SQL, where the table name is text). Recall shadows it with a schema/2 that requires an atom and forwards the equivalent string to Ecto.Schema — so schema :users is what you write, while __schema__(:source) still returns "users" for the rest of Ecto. Passing a string raises at compile time.

Physical layout lives in migrations, not the schema

The schema is purely logical — fields, types, struct, changesets. A table's physical layout (the table type and any secondary indexes) is owned by migrations, the same way a SQL adapter works:

create table(:users) do
  add :name, :string
  add :age, :integer
end

create index(:users, [:age])   # a secondary index the planners can use

The read and join planners serve an equality on the primary key, or on any attribute the table physically indexes, with :mnesia.read/index_read instead of a full scan; an equality on any other field runs correctly but takes the scan path. Indexes are never created on the fly — only by a create index migration — so an ad-hoc query can't silently grow a permanent index.

Generated functions

Given the schema above (fields == [:id, :name, :age], stored as {:users, id, name, age}), the macro emits:

  • __recall__(:tag) — the Mnesia record tag (table name), :users.
  • __recall__(:field_names) — fields in storage order (primary key first), [:id, :name, :age].
  • __recall__(:field_indices)%{id: 0, name: 1, age: 2}.
  • __recall__(:field_count) — the match-spec arity, 3.
  • __recall__(:match_head) / :match_body — the prebuilt match-spec head tuple {:users, :"$1", :"$2", :"$3"} and body list [:"$1", :"$2", :"$3"].
  • __recall_index__(field) — the field's 0-based position, one compiled clause per field (e.g. __recall_index__(:age) returns 2).
  • __recall_position__(field) — the field's 1-based Mnesia attribute position, index + 2 (+1 for the leading tag, +1 for 1-based tuples).
  • __recall__(:load_free?)true when every field stores its native runtime term (so loading is the identity), letting Ecto.Adapters.Recall.FastRead assemble structs without per-field loaders. false for custom types, Ecto enums, embeds, associations.
  • __recall__(:load_fields)[{field, row_index}, ...] for the real (non-pad) struct fields in storage order, the recipe FastRead uses to stamp a stored row onto the loaded struct.

Both per-field accessors raise KeyError for an unknown field.

Summary

Functions

Like Ecto.Schema.schema/2, but the table name must be an atom (the form a Mnesia table actually takes). Forwards the equivalent string to Ecto.Schema.schema/2, so the rest of Ecto sees the source it expects.

Functions

schema(source, arg2)

(macro)

Like Ecto.Schema.schema/2, but the table name must be an atom (the form a Mnesia table actually takes). Forwards the equivalent string to Ecto.Schema.schema/2, so the rest of Ecto sees the source it expects.