kura_schema behaviour (kura v2.20.7)

View Source

Behaviour for defining database-backed schemas.

Implement the table/0 and fields/0 callbacks to define a schema module. Mark one field with primary_key = true. Optionally implement timestamps/0 and associations/0.

-module(my_user).
-behaviour(kura_schema).
-include_lib("kura/include/kura.hrl").

table() -> <<"users">>.
fields() ->
    [#kura_field{name = id, type = id, primary_key = true},
     #kura_field{name = name, type = string},
     #kura_field{name = email, type = string}].

For a composite primary key, implement the optional key/0 callback returning the ordered key columns; primary_key = true markers are then unnecessary. When key/0 is absent the key is derived from the primary_key = true fields, so existing single-key schemas are unchanged.

-module(membership).
-behaviour(kura_schema).
-include_lib("kura/include/kura.hrl").

table() -> <<"memberships">>.
key() -> [org_id, user_id].
fields() ->
    [#kura_field{name = org_id, type = uuid, nullable = false},
     #kura_field{name = user_id, type = uuid, nullable = false},
     #kura_field{name = role, type = string}].

Summary

Types

Referential action for a foreign key when the referenced row is deleted.

Functions

Return the foreign-key column(s) of an association as an ordered list.

Return a many_to_many association's join-table columns as {OwnerCols, RelatedCols}, each an ordered list. A single-column join_keys = {owner, related} is the one-element-list case.

Return the referential action a belongs_to asks for when its parent row is deleted, ready to be placed on the #kura_column{} that owns the key.

Return the target schema module of an association.

Return the explicit referenced key column(s) on the target side, or undefined to mean "default to the target schema's key". Only a #kura_ref{} can carry this; legacy associations always return undefined.

Look up a single association by name.

Return all associations defined on a schema module.

Return map of field name to column name (binary), excluding virtual fields.

Return all table-level constraints defined on a schema module.

Look up a single embed by name.

Return all embeds defined on a schema module.

Return list of field names for a schema module.

Return map of field name to kura type for a schema module.

Generate a primary key value if the schema defines generate_id/0.

Check if a schema module has an after_* hook for the given action.

Return all indexes defined on a schema module.

Return the ordered primary-key column names for a schema module.

Return the ordered primary-key field records for a schema module.

Return list of non-virtual field names.

Return the single primary-key field name for a schema module.

Return the single primary-key field record, or undefined if the schema has no primary key. Raises on a composite key.

Run after_delete hook if defined. Returns ok or {error, Reason}.

Run after_insert hook if defined. Returns {ok, Record} or {error, Reason}.

Run after_update hook if defined. Returns {ok, Record} or {error, Reason}.

Run before_delete hook if defined. Returns ok or {error, Reason}.

Run before_insert hook if defined. Returns {ok, CS} or {error, CS}.

Run before_update hook if defined. Returns {ok, CS} or {error, CS}.

Types

on_delete()

-type on_delete() :: cascade | restrict | set_null | no_action.

Referential action for a foreign key when the referenced row is deleted.

These four are the actions every dialect kura ships renders identically, so a schema that declares one is portable. set_default is deliberately absent: PostgreSQL fails the delete when the column default is not itself a valid parent key, SQLite only enforces it while foreign_keys is on, and MySQL's InnoDB rejects it at DDL time. A dialect-specific action must be introduced dialect-aware rather than added to this union.

Callbacks

after_delete/1

(optional)
-callback after_delete(map()) -> ok | {error, term()}.

after_insert/1

(optional)
-callback after_insert(map()) -> {ok, map()} | {error, term()}.

after_update/1

(optional)
-callback after_update(map()) -> {ok, map()} | {error, term()}.

associations()

(optional)
-callback associations() ->
                          [#kura_assoc{name :: atom(),
                                       type :: belongs_to | has_one | has_many | many_to_many,
                                       schema :: module(),
                                       foreign_key :: atom() | undefined,
                                       ref ::
                                           #kura_ref{fields :: [atom()] | undefined,
                                                     target :: module() | undefined,
                                                     target_key :: [atom()] | undefined} |
                                           undefined,
                                       join_through :: binary() | module() | undefined,
                                       join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                                       through :: [atom()] | undefined,
                                       on_delete :: kura_schema:on_delete() | undefined}].

before_delete/1

(optional)
-callback before_delete(map()) -> ok | {error, term()}.

before_insert/1

(optional)
-callback before_insert(#kura_changeset{valid :: boolean(),
                                        schema :: module() | undefined,
                                        data :: map(),
                                        params :: map(),
                                        changes :: map(),
                                        errors :: [{atom(), binary()}],
                                        types :: #{atom() => kura_types:kura_type()},
                                        required :: [atom()],
                                        action :: atom() | undefined,
                                        constraints ::
                                            [#kura_constraint{type ::
                                                                  unique | foreign_key | check | exclusion,
                                                              constraint :: binary(),
                                                              field :: atom(),
                                                              message :: binary()}],
                                        assoc_changes ::
                                            #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                        prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                        optimistic_lock :: atom() | undefined}) ->
                           {ok,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}} |
                           {error,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}}.

before_update/1

(optional)
-callback before_update(#kura_changeset{valid :: boolean(),
                                        schema :: module() | undefined,
                                        data :: map(),
                                        params :: map(),
                                        changes :: map(),
                                        errors :: [{atom(), binary()}],
                                        types :: #{atom() => kura_types:kura_type()},
                                        required :: [atom()],
                                        action :: atom() | undefined,
                                        constraints ::
                                            [#kura_constraint{type ::
                                                                  unique | foreign_key | check | exclusion,
                                                              constraint :: binary(),
                                                              field :: atom(),
                                                              message :: binary()}],
                                        assoc_changes ::
                                            #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                        prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                        optimistic_lock :: atom() | undefined}) ->
                           {ok,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}} |
                           {error,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}}.

constraints()

(optional)
-callback constraints() -> [kura_migration:table_constraint()].

embeds()

(optional)
-callback embeds() -> [#kura_embed{name :: atom(), type :: embeds_one | embeds_many, schema :: module()}].

fields()

-callback fields() ->
                    [#kura_field{name :: atom(),
                                 type :: kura_types:kura_type(),
                                 column :: binary() | undefined,
                                 default :: term(),
                                 primary_key :: boolean(),
                                 nullable :: boolean(),
                                 virtual :: boolean()}].

generate_id()

(optional)
-callback generate_id() -> term().

indexes()

(optional)
-callback indexes() -> [kura_migration:index_def()].

key()

(optional)
-callback key() -> [atom()].

table()

-callback table() -> binary().

timestamps()

(optional)
-callback timestamps() -> [{atom(), kura_types:kura_type()}].

Functions

assoc_fields/1

-spec assoc_fields(#kura_assoc{name :: atom(),
                               type :: belongs_to | has_one | has_many | many_to_many,
                               schema :: module(),
                               foreign_key :: atom() | undefined,
                               ref ::
                                   #kura_ref{fields :: [atom()] | undefined,
                                             target :: module() | undefined,
                                             target_key :: [atom()] | undefined} |
                                   undefined,
                               join_through :: binary() | module() | undefined,
                               join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                               through :: [atom()] | undefined,
                               on_delete :: kura_schema:on_delete() | undefined}) ->
                      [atom()].

Return the foreign-key column(s) of an association as an ordered list.

A #kura_ref{}'s fields win; a legacy single foreign_key lowers to a one-element list, so single-column associations are the arity-1 case of the same list. Returns [] if neither is set.

assoc_join_keys/1

-spec assoc_join_keys(#kura_assoc{name :: atom(),
                                  type :: belongs_to | has_one | has_many | many_to_many,
                                  schema :: module(),
                                  foreign_key :: atom() | undefined,
                                  ref ::
                                      #kura_ref{fields :: [atom()] | undefined,
                                                target :: module() | undefined,
                                                target_key :: [atom()] | undefined} |
                                      undefined,
                                  join_through :: binary() | module() | undefined,
                                  join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                                  through :: [atom()] | undefined,
                                  on_delete :: kura_schema:on_delete() | undefined}) ->
                         {[atom()], [atom()]}.

Return a many_to_many association's join-table columns as {OwnerCols, RelatedCols}, each an ordered list. A single-column join_keys = {owner, related} is the one-element-list case.

assoc_on_delete/1

-spec assoc_on_delete(#kura_assoc{name :: atom(),
                                  type :: belongs_to | has_one | has_many | many_to_many,
                                  schema :: module(),
                                  foreign_key :: atom() | undefined,
                                  ref ::
                                      #kura_ref{fields :: [atom()] | undefined,
                                                target :: module() | undefined,
                                                target_key :: [atom()] | undefined} |
                                      undefined,
                                  join_through :: binary() | module() | undefined,
                                  join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                                  through :: [atom()] | undefined,
                                  on_delete :: kura_schema:on_delete() | undefined}) ->
                         on_delete() | undefined.

Return the referential action a belongs_to asks for when its parent row is deleted, ready to be placed on the #kura_column{} that owns the key.

belongs_to is the only association that owns a foreign-key column, so it is the only one that can carry the constraint. Declaring on_delete on any other association type raises {on_delete_not_owned, Name, Type} rather than being ignored - the clause would never reach the DDL, and a cascade rule that silently does not exist is worse than a build failure.

An undeclared on_delete on a belongs_to lowers to no_action, which is what generated foreign keys have always emitted. Any other type returns undefined: it owns no column, so it contributes no clause.

assoc_target/1

-spec assoc_target(#kura_assoc{name :: atom(),
                               type :: belongs_to | has_one | has_many | many_to_many,
                               schema :: module(),
                               foreign_key :: atom() | undefined,
                               ref ::
                                   #kura_ref{fields :: [atom()] | undefined,
                                             target :: module() | undefined,
                                             target_key :: [atom()] | undefined} |
                                   undefined,
                               join_through :: binary() | module() | undefined,
                               join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                               through :: [atom()] | undefined,
                               on_delete :: kura_schema:on_delete() | undefined}) ->
                      module() | undefined.

Return the target schema module of an association.

A #kura_ref{}'s target wins; otherwise the legacy schema field.

assoc_target_key/1

-spec assoc_target_key(#kura_assoc{name :: atom(),
                                   type :: belongs_to | has_one | has_many | many_to_many,
                                   schema :: module(),
                                   foreign_key :: atom() | undefined,
                                   ref ::
                                       #kura_ref{fields :: [atom()] | undefined,
                                                 target :: module() | undefined,
                                                 target_key :: [atom()] | undefined} |
                                       undefined,
                                   join_through :: binary() | module() | undefined,
                                   join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                                   through :: [atom()] | undefined,
                                   on_delete :: kura_schema:on_delete() | undefined}) ->
                          [atom()] | undefined.

Return the explicit referenced key column(s) on the target side, or undefined to mean "default to the target schema's key". Only a #kura_ref{} can carry this; legacy associations always return undefined.

association(Mod, Name)

-spec association(module(), atom()) ->
                     {ok,
                      #kura_assoc{name :: atom(),
                                  type :: belongs_to | has_one | has_many | many_to_many,
                                  schema :: module(),
                                  foreign_key :: atom() | undefined,
                                  ref ::
                                      #kura_ref{fields :: [atom()] | undefined,
                                                target :: module() | undefined,
                                                target_key :: [atom()] | undefined} |
                                      undefined,
                                  join_through :: binary() | module() | undefined,
                                  join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                                  through :: [atom()] | undefined,
                                  on_delete :: kura_schema:on_delete() | undefined}} |
                     {error, not_found}.

Look up a single association by name.

associations(Mod)

-spec associations(module()) ->
                      [#kura_assoc{name :: atom(),
                                   type :: belongs_to | has_one | has_many | many_to_many,
                                   schema :: module(),
                                   foreign_key :: atom() | undefined,
                                   ref ::
                                       #kura_ref{fields :: [atom()] | undefined,
                                                 target :: module() | undefined,
                                                 target_key :: [atom()] | undefined} |
                                       undefined,
                                   join_through :: binary() | module() | undefined,
                                   join_keys :: {atom() | [atom()], atom() | [atom()]} | undefined,
                                   through :: [atom()] | undefined,
                                   on_delete :: kura_schema:on_delete() | undefined}].

Return all associations defined on a schema module.

column_map(Mod)

-spec column_map(module()) -> #{atom() => binary()}.

Return map of field name to column name (binary), excluding virtual fields.

constraints(Mod)

-spec constraints(module()) -> [kura_migration:table_constraint()].

Return all table-level constraints defined on a schema module.

embed(Mod, Name)

-spec embed(module(), atom()) ->
               {ok, #kura_embed{name :: atom(), type :: embeds_one | embeds_many, schema :: module()}} |
               {error, not_found}.

Look up a single embed by name.

embeds(Mod)

-spec embeds(module()) ->
                [#kura_embed{name :: atom(), type :: embeds_one | embeds_many, schema :: module()}].

Return all embeds defined on a schema module.

field_names(Mod)

-spec field_names(module()) -> [atom()].

Return list of field names for a schema module.

field_types(Mod)

-spec field_types(module()) -> #{atom() => kura_types:kura_type()}.

Return map of field name to kura type for a schema module.

generate_id(Mod)

-spec generate_id(module()) -> {ok, term()} | undefined.

Generate a primary key value if the schema defines generate_id/0.

has_after_hook(Mod, Action)

-spec has_after_hook(module(), insert | update | delete) -> boolean().

Check if a schema module has an after_* hook for the given action.

indexes(Mod)

-spec indexes(module()) -> [kura_migration:index_def()].

Return all indexes defined on a schema module.

key(Mod)

-spec key(module()) -> [atom()].

Return the ordered primary-key column names for a schema module.

A schema declares its key with the optional key/0 callback (an ordered list, arity 1 or more for composite keys). When key/0 is not implemented, the key is derived from the fields marked primary_key = true, so existing single-key schemas need no change.

key_fields(Mod)

-spec key_fields(module()) ->
                    [#kura_field{name :: atom(),
                                 type :: kura_types:kura_type(),
                                 column :: binary() | undefined,
                                 default :: term(),
                                 primary_key :: boolean(),
                                 nullable :: boolean(),
                                 virtual :: boolean()}].

Return the ordered primary-key field records for a schema module.

non_virtual_fields(Mod)

-spec non_virtual_fields(module()) -> [atom()].

Return list of non-virtual field names.

primary_key(Mod)

-spec primary_key(module()) -> atom().

Return the single primary-key field name for a schema module.

Convenience over key/1 for the common single-key case; raises {composite_primary_key, Mod, Keys} on a composite key.

primary_key_field(Mod)

-spec primary_key_field(module()) ->
                           #kura_field{name :: atom(),
                                       type :: kura_types:kura_type(),
                                       column :: binary() | undefined,
                                       default :: term(),
                                       primary_key :: boolean(),
                                       nullable :: boolean(),
                                       virtual :: boolean()} |
                           undefined.

Return the single primary-key field record, or undefined if the schema has no primary key. Raises on a composite key.

run_after_delete(Mod, Record)

-spec run_after_delete(module(), map()) -> ok | {error, term()}.

Run after_delete hook if defined. Returns ok or {error, Reason}.

run_after_insert(Mod, Record)

-spec run_after_insert(module(), map()) -> {ok, map()} | {error, term()}.

Run after_insert hook if defined. Returns {ok, Record} or {error, Reason}.

run_after_update(Mod, Record)

-spec run_after_update(module(), map()) -> {ok, map()} | {error, term()}.

Run after_update hook if defined. Returns {ok, Record} or {error, Reason}.

run_before_delete(Mod, Record)

-spec run_before_delete(module(), map()) -> ok | {error, term()}.

Run before_delete hook if defined. Returns ok or {error, Reason}.

run_before_insert(Mod, CS)

-spec run_before_insert(module(),
                        #kura_changeset{valid :: boolean(),
                                        schema :: module() | undefined,
                                        data :: map(),
                                        params :: map(),
                                        changes :: map(),
                                        errors :: [{atom(), binary()}],
                                        types :: #{atom() => kura_types:kura_type()},
                                        required :: [atom()],
                                        action :: atom() | undefined,
                                        constraints ::
                                            [#kura_constraint{type ::
                                                                  unique | foreign_key | check |
                                                                  exclusion,
                                                              constraint :: binary(),
                                                              field :: atom(),
                                                              message :: binary()}],
                                        assoc_changes ::
                                            #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                        prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                        optimistic_lock :: atom() | undefined}) ->
                           {ok,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}} |
                           {error,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}}.

Run before_insert hook if defined. Returns {ok, CS} or {error, CS}.

run_before_update(Mod, CS)

-spec run_before_update(module(),
                        #kura_changeset{valid :: boolean(),
                                        schema :: module() | undefined,
                                        data :: map(),
                                        params :: map(),
                                        changes :: map(),
                                        errors :: [{atom(), binary()}],
                                        types :: #{atom() => kura_types:kura_type()},
                                        required :: [atom()],
                                        action :: atom() | undefined,
                                        constraints ::
                                            [#kura_constraint{type ::
                                                                  unique | foreign_key | check |
                                                                  exclusion,
                                                              constraint :: binary(),
                                                              field :: atom(),
                                                              message :: binary()}],
                                        assoc_changes ::
                                            #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                        prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                        optimistic_lock :: atom() | undefined}) ->
                           {ok,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}} |
                           {error,
                            #kura_changeset{valid :: boolean(),
                                            schema :: module() | undefined,
                                            data :: map(),
                                            params :: map(),
                                            changes :: map(),
                                            errors :: [{atom(), binary()}],
                                            types :: #{atom() => kura_types:kura_type()},
                                            required :: [atom()],
                                            action :: atom() | undefined,
                                            constraints ::
                                                [#kura_constraint{type ::
                                                                      unique | foreign_key | check |
                                                                      exclusion,
                                                                  constraint :: binary(),
                                                                  field :: atom(),
                                                                  message :: binary()}],
                                            assoc_changes ::
                                                #{atom() => #kura_changeset{} | [#kura_changeset{}]},
                                            prepare :: [fun((#kura_changeset{}) -> #kura_changeset{})],
                                            optimistic_lock :: atom() | undefined}}.

Run before_update hook if defined. Returns {ok, CS} or {error, CS}.