kura_schema behaviour (kura v2.17.1)

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

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 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}.

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}].

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}) ->
                      [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}) ->
                         {[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_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}) ->
                      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}) ->
                          [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}} |
                     {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}].

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}.