¿Administrar contadores incrementales en mnesia DBMS?

Me he dado cuenta de que mnesia no es compatible con la función de incremento automático como lo hace MySQL u otro RDBMS. Los contadores mencionados en la documentación de mnesia no están realmente bien explicados. Por ejemplo, he encontrado sofar una función en toda la documentación que manipula contadores

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

Entonces, esto me ha molestado por un tiempo porque funciona con registros de tipo

{TabName, Key, Integer}
Esto tampoco está claro y posiblemente porque ningún libro de erlang o documentación de mnesia proporciona un ejemplo para explicarlo. Esto me ha obligado a implementar mis propias API de manipulación de contadores. Como quería poder acceder y administrar mis registros usando contadores, tuve que incluir un campo llamado 'contador' en mis registros, luego, en el momento de agregar el registro en la tabla que pretende tener contadores, lo hago así:

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

La posición de los campos del contador no importa. La API se implementa de la siguiente manera:

%% @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 esto, si el campo contador no es un atributo de la tabla, dejo a la fuerza que el proceso que intenta ejecutar este código salga con un motivo, por lo que si los programadores llaman a esto dentro de un intento ... captura, o un caso ( captura ...) del cuerpo, verían fácilmente lo que está mal. Alternativamente, podría preguntar si este fragmento de código se está ejecutando dentro de una transacción usandomnesia:is_transaction() y si esto es cierto, llamomnesia:abort/1, si es falso, podría salir con razón. Además, uso mnesia_frag en la función de actividad mnesia porque esta implementación funcionará independientemente de las propiedades de fragmentación de una tabla. Si uso elmnesia:transaction(Fun), las tablas fragmentadas se volverán inconsistentes porque esta llamada solo accederá al fragmento inicial (la tabla base).
Ahora, cuando se elimina un registro de la tabla con contadores, necesitamos reorganizar el orden en la tabla. Esta operación es costosa ya que requiere iteración en toda la tabla. Porque si eliminan un registro cuyo contador = 5, el que tiene contador = 6 debe convertirse en contador = 5 y así sucesivamente siguiendo el patrón. Todos los registros cuyos contadores fueron mayores que el eliminado, deben ser decrementados. Entonces, al pasar el valor del contador eliminado y el TableName, es posible iterar sobre la tabla usando
mnesia:foldl/3 or mnesia:foldr/3 , the difference between these two comes in only with ordered table types
Aquí está la función que se encarga de esto:

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.


Observa que tengo un código que me ayuda a encontrar la posición del campo del contador dinámicamente desde el registro. El código que me ayuda a hacer esto se muestra a continuación:

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


Esto es así porque este módulo no debe conocer ninguno de los registros de los programadores para ayudarlo con los contadores. Por lo tanto, para acceder al valor del contador desde el registro utilizando funciones de manipulación de tuplas comoelement(N::integer(),Tuple::tuple()), tengo que calcular su posición en la representación de tupla del registro, dinámicamente.

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

Por ejemplo, usando qlc (comprensión de la lista de consultas) para consultar tablas con restricciones dinámicas, considere estos códigos a continuación:

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

En un sistema de gestión de existencias, esto 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>

Esto es trabajo para mí. Por favor, avísenme de qué otra forma debería ocuparme de los contadores, o podrían decirme cómo los manejan por ese lado.

Respuestas a la pregunta(1)

Su respuesta a la pregunta