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/1
Alé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 usandomnesia: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.