我意识到 mnesia 不像 MySQL 或其他 RDBMS 那样支持自动增量功能。mnesia 文档中谈到的计数器并没有得到很好的解释。例如,到目前为止,我在整个文档中发现了一个操作计数器的函数
mnesia:dirty_update_counter({Tab::atom(),Key::any()}, Val::positive_integer())
所以,这让我困扰了一段时间,因为它适用于类型记录
{TabName, Key, Integer}
This is also unclear and possibly because no erlang book or mnesia documentation provides an example to explain it.This has forced me to implement my own counter manipulation APIs.Since i wanted to be able to access and manage my records using counters, i had to include a field called 'counter' in my records, then at the point of adding the record in the table which is intended to have counters, i do it like this:
#recordname{field1 = Val1,...,counter = auto_increment(?THIS_TABLE)}
计数器字段的位置并不重要。 API 的实现如下:
%% @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).
为了解释这一点,如果计数器字段不是表的属性,我会强制让尝试执行此代码的进程以某种原因退出,因此如果程序员在 try ...catch 或 case (抓住...)的身体,他们很容易看出什么地方出了问题。或者,我可以询问此代码片段是否正在事务中执行,方法是使用mnesia:is_transaction()
如果这返回 true,我打电话mnesia:abort/1
,如果为 false,我可以有理由退出。另外,我在 mnesia 活动函数中使用 mnesia_frag,因为无论表的碎片属性如何,此实现都将起作用。如果我使用mnesia:transaction(Fun)
,碎片表将变得不一致,因为此调用将仅访问初始碎片(基表)。
现在,当从带有计数器的表中删除一条记录时,我们需要重新排列表中的顺序。此操作的成本很高,因为它需要遍历整个表。因为如果删除 counter = 5 的记录,则 counter = 6 的记录必须变为 counter = 5,依此类推。计数器大于已删除计数器的所有记录都必须递减。因此,通过传递已删除的计数器值和 TableName,可以使用以下方式迭代表
mnesia:foldl/3 or mnesia:foldr/3 , the difference between these two comes in only with ordered table types
这是处理此问题的函数:
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.
您注意到我有代码可以帮助我从记录中动态查找计数器字段的位置。帮助我做到这一点的代码如下所示:
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).
之所以如此,是因为该模块不能知道程序员的任何记录来帮助他使用计数器。因此,为了使用元组操作函数从记录中访问计数器的值,例如element(N::integer(),Tuple::tuple())
,我必须动态计算它在记录的元组表示中的位置。
These two functions have worked for me and are still working till auto_increment
is implemented in mnesia.
For example, using qlc (query list comprehension) to query tables with dynamic constraints, consider these pieces of code below:
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).
在库存管理系统中,这是有效的......
([email protected] /cdn-cgi/l/email-protection)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] /cdn-cgi/l/email-protection)7>
这对我有用。请告诉我我应该如何处理柜台。或者你可以告诉我你如何在那边处理它们。