Skip to content
This repository
Newer
Older
100644 246 lines (212 sloc) 8.203 kb
d0360738 »
2012-02-04 BossDB is now its own project
1 -module(boss_db_adapter_mnesia).
2 -behaviour(boss_db_adapter).
e0c6e907 »
2012-02-25 start/stop for app, init/terminate for connection
3 -export([init/1, terminate/1, start/1, stop/0, find/2, find/7]).
d0360738 »
2012-02-04 BossDB is now its own project
4 -export([count/3, counter/2, incr/3, delete/2, save_record/2]).
5 -export([transaction/2]).
6
7 %-define(TRILLION, (1000 * 1000 * 1000 * 1000)).
8
e0c6e907 »
2012-02-25 start/stop for app, init/terminate for connection
9 start(_) ->
fd2df1d0 »
2012-02-10 Manage connections with Poolboy
10 application:start(mnesia).
11
e0c6e907 »
2012-02-25 start/stop for app, init/terminate for connection
12 stop() ->
13 application:stop(mnesia).
14
d0360738 »
2012-02-04 BossDB is now its own project
15 % -----
16
e0c6e907 »
2012-02-25 start/stop for app, init/terminate for connection
17 init(_Options) ->
d0360738 »
2012-02-04 BossDB is now its own project
18 {ok, undefined}.
19
20 % -----
e0c6e907 »
2012-02-25 start/stop for app, init/terminate for connection
21 terminate(_) ->
22 ok.
d0360738 »
2012-02-04 BossDB is now its own project
23
24 % -----
25 find(_, Id) when is_list(Id) ->
26 Type = infer_type_from_id(Id),
27 Fun = fun () -> mnesia:read(Type,Id) end,
28 case mnesia:transaction(Fun) of
29 {atomic,[]} ->
30 undefined;
31 {atomic,[Record]} -> % I dont like this - we should really be checking that we only got 1 record
32 % io:format("Record is ~p~n",[Record]),
33 case boss_record_lib:ensure_loaded(Type) of
34 true ->
35 Record;
36 false ->
37 {error, {module_not_loaded, Type}}
38 end;
39 {aborted, Reason} ->
40 {error, Reason}
41 end.
42
43 % -----
44 find(_, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions),
45 is_integer(Max) orelse Max =:= all,
46 is_integer(Skip), is_atom(Sort), is_atom(SortOrder) ->
47 % Mnesia allows a pattern to be provided against which it will check records.
48 % This allows 'eq' conditions to be handled by Mnesia itself. The list of remaining
49 % conditions form a 'filter' against which each record returned by Mnesia is tested here.
50 % So...the first job here is to split the Conditions into a Pattern and a 'Filter'.
51 case boss_record_lib:ensure_loaded(Type) of
52 true ->
53 {Pattern, Filter} = build_query(Type, Conditions, Max, Skip, Sort, SortOrder),
54 RawList = mnesia:dirty_match_object(list_to_tuple([Type | Pattern])),
55 FilteredList = apply_filters(RawList, Filter),
56 SortedList = apply_sort(FilteredList, Sort, SortOrder),
57 SkippedList = apply_skip(SortedList, Skip),
58 MaxList = apply_max(SkippedList, Max),
59 MaxList;
60 false ->
61 []
62 end.
63
64 apply_filters(List, Filters) ->
65 apply_filters(List, Filters, []).
66
67 apply_filters([],_Filters,Acc) ->
68 Acc;
69 apply_filters([First|Rest],Filters,Acc) ->
70 case filter_rec(First,Filters) of
71 keep ->
72 apply_filters(Rest,Filters,[First|Acc]);
73 drop ->
74 apply_filters(Rest,Filters,Acc)
75 end.
76
77 filter_rec(_Rec, []) ->
78 keep;
79 filter_rec(Rec, [First|Rest]) ->
80 case test_rec(Rec, First) of
81 true ->
82 filter_rec(Rec,Rest);
83 false ->
84 drop
85 end.
86
87 apply_sort([], _Key, _Order) ->
88 [];
89 apply_sort(List, primary, Order) ->
90 apply_sort(List, id, Order);
91 apply_sort(List, Key, ascending) ->
92 Fun = fun (A, B) -> apply(A,Key,[]) =< apply(B,Key,[]) end,
93 lists:sort(Fun, List);
94 apply_sort(List, Key, descending) ->
95 Fun = fun (A, B) -> apply(A,Key,[]) >= apply(B,Key,[]) end,
96 lists:sort(Fun, List).
97
98
99
100 apply_skip(List, 0) ->
101 List;
102 apply_skip(List, Skip) when Skip >= length(List) ->
103 [];
104 apply_skip(List, Skip) ->
105 lists:nthtail(Skip, List).
106
107 apply_max(List, all) ->
108 List;
109 apply_max(List, Max) when is_integer(Max) ->
110 lists:sublist(List, Max).
111
112 test_rec(Rec,{Key, 'not_equals', Value}) ->
113 apply(Rec,Key,[]) /= Value;
114 test_rec(Rec,{Key, 'in', Value}) when is_list(Value) ->
115 lists:member(apply(Rec,Key,[]), Value) ;
116 test_rec(Rec,{Key, 'not_in', Value}) when is_list(Value) ->
117 not lists:member(apply(Rec,Key,[]), Value) ;
118 test_rec(Rec,{Key, 'in', {Min, Max}}) when Max >= Min ->
119 Fld = apply(Rec,Key,[]),
120 (Fld >= Min) and (Fld =< Max);
121 test_rec(Rec,{Key, 'not_in', {Min, Max}}) when Max >= Min ->
122 Fld = apply(Rec,Key,[]),
123 (Fld < Min) or (Fld > Max);
124 test_rec(Rec,{Key, 'gt', Value}) ->
125 apply(Rec,Key,[]) > Value;
126 test_rec(Rec,{Key, 'lt', Value}) ->
127 apply(Rec,Key,[]) < Value;
128 test_rec(Rec,{Key, 'ge', Value}) ->
129 apply(Rec,Key,[]) >= Value;
8d71781e »
2012-09-03 Fixed ge/le mnesia db adaptor find sort order: they were reversed.
130 test_rec(Rec,{Key, 'le', Value}) ->
131 apply(Rec,Key,[]) =< Value;
86e57291 »
2012-09-03 Actually support case-insensitive search in Mnesia
132 test_rec(Rec,{Key, 'matches', "*"++Value}) ->
133 {ok, MP} = re:compile(Value, [caseless]),
d0360738 »
2012-02-04 BossDB is now its own project
134 case re:run(apply(Rec,Key,[]), MP) of
277c6d75 »
2012-09-03 Support case-insenstive regexes
135 {match,_} -> true;
136 match -> true;
137 _ -> false
138 end;
86e57291 »
2012-09-03 Actually support case-insensitive search in Mnesia
139 test_rec(Rec,{Key, 'matches', Value}) ->
140 {ok, MP} = re:compile(Value),
277c6d75 »
2012-09-03 Support case-insenstive regexes
141 case re:run(apply(Rec,Key,[]), MP) of
142 {match,_} -> true;
143 match -> true;
144 _ -> false
d0360738 »
2012-02-04 BossDB is now its own project
145 end;
146 test_rec(Rec,{Key, 'not_matches', Value}) ->
147 not test_rec(Rec,{Key, 'matches', Value});
148 test_rec(Rec,{Key, 'contains', Value}) ->
149 lists:member(Value,apply(Rec,Key,[]));
150 test_rec(Rec,{Key, 'not_contains', Value}) ->
151 not lists:member(Value,apply(Rec,Key,[]));
152 test_rec(Rec,{Key, 'contains_all', Values}) when is_list(Values) ->
153 lists:all(fun (Ele) -> lists:member(Ele, apply(Rec,Key,[])) end, Values);
154 test_rec(Rec,{Key, 'not_contains_all', Values}) when is_list(Values) ->
155 lists:any(fun (Ele) -> not lists:member(Ele, apply(Rec,Key,[])) end, Values);
156 test_rec(Rec,{Key, 'contains_any', Values}) when is_list(Values) ->
157 lists:any(fun (Ele) -> lists:member(Ele, apply(Rec,Key,[])) end, Values);
158 test_rec(Rec,{Key, 'contains_none', Values}) when is_list(Values) ->
159 lists:any(fun (Ele) -> not lists:member(Ele, apply(Rec,Key,[])) end, Values).
160
161 % -----
162 count(Conn, Type, Conditions) ->
cbbb45e9 »
2012-04-18 Fix "count" in the mnesia adapter.
163 length(find(Conn, Type, Conditions, all, 0, id, ascending)).
d0360738 »
2012-02-04 BossDB is now its own project
164
165 % -----
166 counter(Conn, Id) when is_list(Id) ->
167 counter(Conn, list_to_binary(Id));
168 counter(_, Id) when is_binary(Id) ->
169 mnesia:dirty_update_counter('_ids_', Id, 0).
170
171 % -----
172 incr(Conn, Id, Count) when is_list(Id) ->
173 incr(Conn, list_to_binary(Id), Count);
174 incr(_, Id, Count) ->
175 mnesia:dirty_update_counter('_ids_', Id, Count).
176
177 % -----
178 delete(Conn, Id) when is_binary(Id) ->
179 %io:format("==> Delete/1 Called with binary ~p~n",[Id]),
180 delete(Conn, binary_to_list(Id));
181 delete(_, Id) when is_list(Id) ->
182 %io:format("==> Delete/1 Called with list ~p~n",[Id]),
183 Type = infer_type_from_id(Id),
184 Fun = fun () -> mnesia:delete({Type,Id}) end,
185 case mnesia:transaction(Fun) of
186 {atomic,ok} ->
187 ok;
188 {aborted, Reason} ->
189 {error, Reason}
190 end.
191
192 % -----
193 save_record(_, Record) when is_tuple(Record) ->
194 Type = element(1, Record),
195 Id = case Record:id() of
196 id ->
197 atom_to_list(Type) ++ "-" ++ integer_to_list(gen_uid(Type));
198 Defined ->
199 Defined
200 end,
201 RecordWithId = Record:set(id, Id),
202
203 Fun = fun() -> mnesia:write(Type, RecordWithId, write) end,
204
205 case mnesia:transaction(Fun) of
206 {atomic, ok} ->
207 {ok, RecordWithId};
208 {aborted, Reason} ->
209 {error, Reason}
210 end.
211
212 transaction(_, TransactionFun) when is_function(TransactionFun) ->
213 mnesia:transaction(TransactionFun).
214
215 % -----
216
217 gen_uid(Tab) ->
218 mnesia:dirty_update_counter('_ids_', Tab, 1).
219
220 %-----
221 infer_type_from_id(Id) when is_binary(Id) ->
222 infer_type_from_id(binary_to_list(Id));
223 infer_type_from_id(Id) when is_list(Id) ->
224 list_to_atom(hd(string:tokens(Id, "-"))).
225
226 %-----
227 build_query(Type, Conditions, _Max, _Skip, _Sort, _SortOrder) -> % a Query is a {Pattern, Filter} combo
228 Fldnames = mnesia:table_info(Type, attributes),
229 BlankPattern = [ {Fld, '_'} || Fld <- Fldnames],
230 {Pattern, Filter} = build_conditions(BlankPattern, [], Conditions),
231 {[proplists:get_value(Fldname, Pattern) || Fldname <- Fldnames], Filter}.
232
233 build_conditions(Pattern, Filter, Conditions) ->
234 build_conditions1(Conditions, Pattern, Filter).
235
236 build_conditions1([], Pattern, Filter) ->
237 {Pattern, Filter};
238 build_conditions1([{Key, 'equals', Value}|Rest], Pattern, Filter) ->
239 build_conditions1([{Key, 'eq', Value}|Rest], Pattern, Filter);
240 build_conditions1([{Key, 'eq', Value}|Rest], Pattern, Filter) ->
241 build_conditions1(Rest, lists:keystore(Key, 1, Pattern, {Key, Value}), Filter);
242 build_conditions1([First|Rest], Pattern, Filter) ->
243 build_conditions1(Rest, Pattern, [First|Filter]).
244
245
Something went wrong with that request. Please try again.