<commit_before>%%%-------------------------------------------------------------------
%%% @author <NAME>
%%% @copyright (C) 2017 ACK CYFRONET AGH
%%% This software is released under the MIT license
%%% cited in 'LICENSE.txt'.
%%% @end
%%%-------------------------------------------------------------------
%%% @doc
%%% This module provides datastore model API.
%%% @end
%%%-------------------------------------------------------------------
-module(datastore_model).
-author("<NAME>").

-include("modules/datastore/datastore_links.hrl").
-include("modules/datastore/datastore_models.hrl").
-include("global_definitions.hrl").


%% API
-export([init/1, get_unique_key/2]).
-export([create/2, save/2, update/3, update/4]).
-export([get/2, exists/2]).
-export([delete/2, delete/3, delete_all/1]).
-export([fold/3, fold/5, fold_keys/3]).
-export([add_links/4, check_and_add_links/5, get_links/4, delete_links/4, mark_links_deleted/4]).
-export([fold_links/6]).
-export([get_links_trees/2]).
%% for rpc
-export([datastore_apply_all_subtrees/4]).

-type model() :: module().
-type record() :: tuple().
-type record_struct() :: datastore_json:record_struct().
-type record_version() :: datastore_versions:record_version().
-type ctx() :: datastore:ctx().
-type key() :: datastore:key().
-type doc() :: datastore_doc:doc(record()).
-type diff() :: datastore_doc:diff(record()).
-type pred() :: datastore_doc:pred(record()).
-type link_name() :: datastore:link_name().
-type link_target() :: datastore:link_target().
-type link_rev() :: datastore:link_rev().
-type link() :: datastore:link().
-type tree_id() :: datastore:tree_id().
-type tree_ids() :: datastore:tree_ids().
-type fold_fun(Sub) :: datastore:fold_fun(Sub).
-type fold_acc() :: datastore:fold_acc().
-type fold_opts() :: datastore:fold_opts().
-type one_or_many(Type) :: Type | [Type].

-export_type([model/0, record/0, record_struct/0, record_version/0,
    key/0, ctx/0, diff/0, fold_opts/0, tree_ids/0]).

% Default time in seconds for document to expire after delete (one year)
-define(EXPIRY, 31536000).

%%--------------------------------------------------------------------
%% @doc
%% Initializes memory driver of a datastore model.
%% @end
%%--------------------------------------------------------------------
-spec init(ctx()) -> ok | {error, term()}.
init(#{memory_driver := undefined}) ->
    ok;
init(#{
    memory_driver := Driver,
    memory_driver_ctx := Ctx,
    memory_driver_opts := Opts
}) ->
    lists:foldl(fun
        (Ctx2, ok) ->
            Driver:init(Ctx2, Opts);
        (_, Error) ->
            Error
    end, ok, datastore_multiplier:get_names(Ctx));
init(Ctx) ->
    init(datastore_model_default:set_defaults(Ctx)).

%%--------------------------------------------------------------------
%% @doc
%% Returns key that is unique between different models.
%% @end
%%--------------------------------------------------------------------
-spec get_unique_key(ctx(), key()) -> key().
% TODO - VFS-3975 - allow routing via generic key without model
% problem with links application that need such routing key
get_unique_key(#{model := Model}, Key) ->
    datastore_key:build_adjacent(atom_to_binary(Model, utf8), Key).

%%--------------------------------------------------------------------
%% @doc
%% Creates model document in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec create(ctx(), doc()) -> {ok, doc()} | {error, term()}.
create(Ctx, Doc = #document{key = undefined}) ->
    save(Ctx, Doc);
create(Ctx, Doc = #document{key = Key}) ->
    Result = datastore_apply(Ctx, Key, fun datastore:create/3, create, [Doc]),
    add_fold_link(Ctx, Key, Result).

%%--------------------------------------------------------------------
%% @doc
%% Saves model document in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec save(ctx(), doc()) -> {ok, doc()} | {error, term()}.
save(Ctx, Doc = #document{key = undefined}) ->
    Ctx2 = Ctx#{generated_key => true},
    Doc2 = Doc#document{key = datastore_key:new()},
    save(Ctx2, Doc2);
save(Ctx, Doc = #document{key = Key}) ->
    Result = datastore_apply(Ctx, Key, fun datastore:save/3, save, [Doc]),
    add_fold_link(Ctx, Key, Result).

%%--------------------------------------------------------------------
%% @doc
%% Updates model document in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec update(ctx(), key(), diff()) -> {ok, doc()} | {error, term()}.
update(Ctx, Key, Diff) ->
    datastore_apply(Ctx, Key, fun datastore:update/3, update, [Diff]).

%%--------------------------------------------------------------------
%% @doc
%% Updates model document in a datastore. If document is missing,
%% creates default one.
%% @end
%%--------------------------------------------------------------------
-spec update(ctx(), key(), diff(), record() | doc()) ->
    {ok, doc()} | {error, term()}.
update(Ctx, Key, Diff, Default = #document{}) ->
    Result = datastore_apply(Ctx, Key, fun datastore:update/4, update, [
        Diff, Default
    ]),
    add_fold_link(Ctx, Key, Result);
update(Ctx, Key, Diff, Default) ->
    update(Ctx, Key, Diff, #document{key = Key, value = Default}).

%%--------------------------------------------------------------------
%% @doc
%% Returns model document from a datastore.
%% @end
%%--------------------------------------------------------------------
-spec get(ctx(), key()) -> {ok, doc()} | {error, term()}.
get(Ctx, Key) ->
    datastore_apply(Ctx, Key, fun datastore:get/2, get, []).

%%--------------------------------------------------------------------
%% @doc
%% Checks whether model document exists in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec exists(ctx(), key()) -> {ok, boolean()} | {error, term()}.
exists(Ctx, Key) ->
    datastore_apply(Ctx, Key, fun datastore:exists/2, exists, []).

%%--------------------------------------------------------------------
%% @doc
%% Removes model document from a datastore.
%% @end
%%--------------------------------------------------------------------
-spec delete(ctx(), key()) -> ok | {error, term()}.
delete(Ctx, Key) ->
    delete(Ctx, Key, fun(_) -> true end).

%%--------------------------------------------------------------------
%% @doc
%% Removes model document from a datastore if a predicate is satisfied.
%% @end
%%--------------------------------------------------------------------
-spec delete(ctx(), key(), pred()) -> ok | {error, term()}.
delete(#{disc_driver := undefined} = Ctx, Key, Pred) ->
    Result = datastore_apply(Ctx, Key, fun datastore:delete/3, delete, [Pred]),
    delete_all_links(Ctx, Key, Result),
    delete_fold_link(Ctx, Key, Result);
delete(Ctx0, Key, Pred) ->
    Expiry = application:get_env(?CLUSTER_WORKER_APP_NAME,
        document_expiry, ?EXPIRY),
    Ctx = couchbase_driver:set_expiry(Ctx0, Expiry),
    Result = datastore_apply(Ctx, Key, fun datastore:delete/3, delete, [Pred]),
    delete_all_links(Ctx0, Key, Result),
    delete_fold_link(Ctx0, Key, Result).

%%--------------------------------------------------------------------
%% @doc
%% Removes all model documents from a datastore.
%% WARNING: should be used only on models that save all documents
%% with volatile flag.
%% @end
%%--------------------------------------------------------------------
-spec delete_all(ctx()) -> ok | {error, term()}.<commit_msg>VFS-6257 Improve variable names in datastore_model<commit_after>%%%-------------------------------------------------------------------
%%% @author <NAME>
%%% @copyright (C) 2017 ACK CYFRONET AGH
%%% This software is released under the MIT license
%%% cited in 'LICENSE.txt'.
%%% @end
%%%-------------------------------------------------------------------
%%% @doc
%%% This module provides datastore model API.
%%% @end
%%%-------------------------------------------------------------------
-module(datastore_model).
-author("<NAME>").

-include("modules/datastore/datastore_links.hrl").
-include("modules/datastore/datastore_models.hrl").
-include("global_definitions.hrl").


%% API
-export([init/1, get_unique_key/2]).
-export([create/2, save/2, update/3, update/4]).
-export([get/2, exists/2]).
-export([delete/2, delete/3, delete_all/1]).
-export([fold/3, fold/5, fold_keys/3]).
-export([add_links/4, check_and_add_links/5, get_links/4, delete_links/4, mark_links_deleted/4]).
-export([fold_links/6]).
-export([get_links_trees/2]).
%% for rpc
-export([datastore_apply_all_subtrees/4]).

-type model() :: module().
-type record() :: tuple().
-type record_struct() :: datastore_json:record_struct().
-type record_version() :: datastore_versions:record_version().
-type ctx() :: datastore:ctx().
-type key() :: datastore:key().
-type doc() :: datastore_doc:doc(record()).
-type diff() :: datastore_doc:diff(record()).
-type pred() :: datastore_doc:pred(record()).
-type link_name() :: datastore:link_name().
-type link_target() :: datastore:link_target().
-type link_rev() :: datastore:link_rev().
-type link() :: datastore:link().
-type tree_id() :: datastore:tree_id().
-type tree_ids() :: datastore:tree_ids().
-type fold_fun(Sub) :: datastore:fold_fun(Sub).
-type fold_acc() :: datastore:fold_acc().
-type fold_opts() :: datastore:fold_opts().
-type one_or_many(Type) :: Type | [Type].

-export_type([model/0, record/0, record_struct/0, record_version/0,
    key/0, ctx/0, diff/0, fold_opts/0, tree_ids/0]).

% Default time in seconds for document to expire after delete (one year)
-define(EXPIRY, 31536000).

%%--------------------------------------------------------------------
%% @doc
%% Initializes memory driver of a datastore model.
%% @end
%%--------------------------------------------------------------------
-spec init(ctx()) -> ok | {error, term()}.
init(#{memory_driver := undefined}) ->
    ok;
init(#{
    memory_driver := Driver,
    memory_driver_ctx := Ctx,
    memory_driver_opts := Opts
}) ->
    lists:foldl(fun
        (Ctx2, ok) ->
            Driver:init(Ctx2, Opts);
        (_, Error) ->
            Error
    end, ok, datastore_multiplier:get_names(Ctx));
init(Ctx) ->
    init(datastore_model_default:set_defaults(Ctx)).

%%--------------------------------------------------------------------
%% @doc
%% Returns key that is unique between different models.
%% @end
%%--------------------------------------------------------------------
-spec get_unique_key(ctx(), key()) -> key().
% TODO - VFS-3975 - allow routing via generic key without model
% problem with links application that need such routing key
get_unique_key(#{model := Model}, Key) ->
    datastore_key:build_adjacent(atom_to_binary(Model, utf8), Key).

%%--------------------------------------------------------------------
%% @doc
%% Creates model document in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec create(ctx(), doc()) -> {ok, doc()} | {error, term()}.
create(Ctx, Doc = #document{key = undefined}) ->
    save(Ctx, Doc);
create(Ctx, Doc = #document{key = Key}) ->
    Result = datastore_apply(Ctx, Key, fun datastore:create/3, create, [Doc]),
    add_fold_link(Ctx, Key, Result).

%%--------------------------------------------------------------------
%% @doc
%% Saves model document in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec save(ctx(), doc()) -> {ok, doc()} | {error, term()}.
save(Ctx, Doc = #document{key = undefined}) ->
    Ctx2 = Ctx#{generated_key => true},
    Doc2 = Doc#document{key = datastore_key:new()},
    save(Ctx2, Doc2);
save(Ctx, Doc = #document{key = Key}) ->
    Result = datastore_apply(Ctx, Key, fun datastore:save/3, save, [Doc]),
    add_fold_link(Ctx, Key, Result).

%%--------------------------------------------------------------------
%% @doc
%% Updates model document in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec update(ctx(), key(), diff()) -> {ok, doc()} | {error, term()}.
update(Ctx, Key, Diff) ->
    datastore_apply(Ctx, Key, fun datastore:update/3, update, [Diff]).

%%--------------------------------------------------------------------
%% @doc
%% Updates model document in a datastore. If document is missing,
%% creates default one.
%% @end
%%--------------------------------------------------------------------
-spec update(ctx(), key(), diff(), record() | doc()) ->
    {ok, doc()} | {error, term()}.
update(Ctx, Key, Diff, Default = #document{}) ->
    Result = datastore_apply(Ctx, Key, fun datastore:update/4, update, [
        Diff, Default
    ]),
    add_fold_link(Ctx, Key, Result);
update(Ctx, Key, Diff, Default) ->
    update(Ctx, Key, Diff, #document{key = Key, value = Default}).

%%--------------------------------------------------------------------
%% @doc
%% Returns model document from a datastore.
%% @end
%%--------------------------------------------------------------------
-spec get(ctx(), key()) -> {ok, doc()} | {error, term()}.
get(Ctx, Key) ->
    datastore_apply(Ctx, Key, fun datastore:get/2, get, []).

%%--------------------------------------------------------------------
%% @doc
%% Checks whether model document exists in a datastore.
%% @end
%%--------------------------------------------------------------------
-spec exists(ctx(), key()) -> {ok, boolean()} | {error, term()}.
exists(Ctx, Key) ->
    datastore_apply(Ctx, Key, fun datastore:exists/2, exists, []).

%%--------------------------------------------------------------------
%% @doc
%% Removes model document from a datastore.
%% @end
%%--------------------------------------------------------------------
-spec delete(ctx(), key()) -> ok | {error, term()}.
delete(Ctx, Key) ->
    delete(Ctx, Key, fun(_) -> true end).

%%--------------------------------------------------------------------
%% @doc
%% Removes model document from a datastore if a predicate is satisfied.
%% @end
%%--------------------------------------------------------------------
-spec delete(ctx(), key(), pred()) -> ok | {error, term()}.
delete(#{disc_driver := undefined} = Ctx, Key, Pred) ->
    Result = datastore_apply(Ctx, Key, fun datastore:delete/3, delete, [Pred]),
    delete_all_links(Ctx, Key, Result),
    delete_fold_link(Ctx, Key, Result);
delete(Ctx, Key, Pred) ->
    Expiry = application:get_env(?CLUSTER_WORKER_APP_NAME,
        document_expiry, ?EXPIRY),
    CtxWithExpiry = couchbase_driver:set_expiry(Ctx, Expiry),
    Result = datastore_apply(CtxWithExpiry, Key, fun datastore:delete/3, delete, [Pred]),
    delete_all_links(Ctx, Key, Result),
    delete_fold_link(Ctx, Key, Result).

%%--------------------------------------------------------------------
%% @doc
%% Removes all model documents from a datastore.
%% WARNING: should be used only on models that save all documents
%% with volatile flag.
%% @end
%%--------------------------------------------------------------------
-spec delete_all(ctx()) -> ok | {error, term()}.
{'fraction_non_alphanumeric': 0.37644003462742226, 'fraction_numerical': 0.008790037956982086, 'mean_word_length': 1.6822646901232363, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '357217', 'n_tokens_mistral': 4249, 'n_tokens_neox': 4275, 'n_words': 1050}