Gerenciando contadores incrementais no DBMS da mnésia?

Eu percebi que a mnesia não suporta o recurso de incremento automático, como o MySQL ou outro RDBMS. Os contadores mencionados na documentação da mnesia não são muito bem explicados. Por exemplo, eu encontrei uma função simples em toda a documentação que manipula contadores

mnesia:dirty_update_counter({Tab::atom(),Key::any()}, Val::positive_integer())

Então, isso me incomodou por um tempo porque ele trabalha com registros do tipo

{TabName, Key, Integer}
Isso também não é claro e, possivelmente, porque nenhum livro de erlang ou documentação de mnésia fornece um exemplo para explicá-lo. Isso me forçou a implementar minhas próprias APIs de contra manipulação. Desde que eu queria poder acessar e gerenciar meus registros usando contadores, tive que inclua um campo chamado 'counter' nos meus registros e, no momento de adicionar o registro na tabela que se destina a ter contadores, eu faço assim:

#recordname{field1 = Val1,...,counter = auto_increment(?THIS_TABLE)}

A posição dos campos do contador não importa. A API é implementada como esta abaixo:

%% @doc this function is called whenever u are writing a new record in the table
%% by giving its result to the counter field in your record.
%% @end
%%
%% @spec auto_increment(TableName::atom()) -> integer() | exit(Reason)
auto_increment(TableName)-> case lists:member(counter,table_info(TableName,attributes)) of false -> erlang:exit({counter,field,not_found,in_table,TableName}); true -> table_info(TableName,size) + 1 end.
table_info(Tab,Item)-> F = fun({X,Y}) -> mnesia:table_info(X,Y) end, mnesia:activity(transaction,F,[{Tab,Item}],mnesia_frag).

Para explicar isso, se o campo do contador não for um atributo da tabela, forço a saída do processo que está tentando executar esse código por um motivo, portanto, se os programadores o chamarem dentro de uma tentativa ... captura ou um caso ( captura ...) do corpo, eles veriam facilmente o que está errado. Como alternativa, eu poderia perguntar se esse fragmento de código está sendo executado dentro de uma transação usandomnesia:is_transaction() e se isso retornar verdadeiro, eu chamomnesia:abort/1Além disso, eu uso mnesia_frag na função de atividade mnesia porque essa implementação funcionará independentemente das propriedades de fragmentação de uma tabela. Se eu usar o métodomnesia:transaction(Fun), tabelas fragmentadas se tornarão inconsistentes porque essa chamada acessará apenas o fragmento inicial (a tabela base).
Agora, quando um registro é excluído da tabela com contadores, precisamos reorganizar o pedido na tabela. Essa operação é cara, pois requer iteração em toda a tabela. Porque se eles excluírem um registro cujo contador = 5, aquele com contador = 6 deve se tornar contador = 5 e assim por diante, seguindo o padrão. Todos os registros cujos contadores foram maiores que o excluído devem ser decrementados. Portanto, passando o valor do contador excluído e o TableName, é possível iterar sobre a tabela usando
mnesia:foldl/3 or mnesia:foldr/3 , the difference between these two comes in only with ordered table types
Aqui está a função que cuida disso:

auto_decrement(Counter_deleted,TableName)->
    Attrs = table_info(TableName,attributes),
    case lists:member(counter,Attrs) of
        false -> erlang:exit({counter,field,not_found,in_table,TableName});
        true -> 
            Counter_position = position(counter,Attrs) + 1,         
            Iterator =  fun(Rec,_) when element(Counter_position,Rec) > Counter_deleted -> 
                            Count = element(Counter_position,Rec),
                            New_rec = erlang:setelement(Counter_position,Rec,Count - 1),
                            mnesia:write(TableName,New_rec,read),
                            [];
                        (_,_) -> []
                        end,
            Find = fun({Fun,Table}) -> mnesia:foldl(Fun, [],Table) end,
            mnesia:activity(transaction,Find,[{Iterator,TableName}],mnesia_frag)
    end.


Você percebe que eu tenho um código que me ajuda a encontrar a posição do campo do contador dinamicamente a partir do registro. O código que me ajuda a fazer isso é mostrado abaixo:

position(_,[]) -> -1;
position(Value,List)->
    find(lists:member(Value,List),Value,List,1).

find(false,_,_,_) -> -1;
find(true,V,[V|_],N)-> N;
find(true,V,[_|X],N)->
    find(V,X,N + 1).

find(V,[V|_],N)-> N;
find(V,[_|X],N) -> find(V,X,N + 1).


Isso ocorre porque este módulo não deve conhecer nenhum registro dos programadores para ajudá-lo com os contadores. Portanto, para acessar o valor do contador do registro usando funções de manipulação de tupla, comoelement(N::integer(),Tuple::tuple()), tenho que calcular sua posição na representação da tupla do registro, dinamicamente.

These two functions have worked for me and are still working till auto_increment
is implemented in mnesia.

Por exemplo, usando qlc (compreensão da lista de consultas) para consultar tabelas com restrições dinâmicas, considere estas partes de código abaixo:

select(Q)->
    F = fun(QH) -> qlc:e(QH) end,
    mnesia:activity(transaction,F,[Q],mnesia_frag).

read_by_custom_validation(Validation_fun,From_table)->
    select(qlc:q([X || X <- mnesia:table(From_table),Validation_fun(X) == true])).

%% Applying the two functions....
find_records_with_counter(From_this,To_that) when 
is_integer(From_this),is_integer(To_that),To_that > From_this -> F = fun(#recordName{counter = N}) when N >= From_this,N =< To_That -> true; (_) -> false end, read_by_custom_validation(F,TableName).

Em um sistema de gerenciamento de estoque, isso está funcionando ...

([email protected])6> stock:get_items_in_range(1,4).
[#item{item_id = "D694",name = "cement",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 500,units = "bags",unit_cost = 20000,
       state = available,last_modified = undefined,
       category = "building material",counter = 1},
 #item{item_id = "131B",name = "nails",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 20000,units = "kgs",unit_cost = 1000,
       state = available,last_modified = undefined,
       category = "building material",counter = 2},
 #item{item_id = "FDD9",name = "iron sheets",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 20,units = "bars",unit_cost = 50000,
       state = available,last_modified = undefined,
       category = "building material",counter = 3},
 #item{item_id = "09D4",name = "paint",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 30000,units = "tins",unit_cost = 5000,
       state = available,last_modified = undefined,
       category = "building material",counter = 4}]
([email protected])7>

Isso está funcionando para mim. Por favor, informe-me sobre de que outra forma eu devo cuidar dos contadores. Ou você pode me dizer como você está lidando com eles desse lado.

questionAnswers(1)

yourAnswerToTheQuestion