1
-module(gen_storage).
2 
3 -author('stephan@spaceboyz.net').
4 
5 -export([behaviour_info/1]).
6 
7 -export([all_table_hosts/1, table_info/3, create_table/4, delete_table/2,
8 	 add_table_copy/4, add_table_index/3, read/2, write/2, delete/2, delete_object/2,
9 	 read/4, write/4, delete/4, delete_object/4, select/2, select/3, select/4,
10 	 select/5, count_records/2, count_records/3, delete_where/3, dirty_read/2,
11 	 dirty_write/2, dirty_delete/2, dirty_delete_object/2, dirty_read/3,
12 	 dirty_write/3, dirty_delete/3, dirty_delete_object/3, dirty_select/3,
13 	 dirty_count_records/2, dirty_count_records/3, dirty_delete_where/3,
14 	 async_dirty/3, sync_dirty/3, transaction/3, write_lock_table/2]).
15 
16 behaviour_info(callbacks) ->
17     [{table_info, 1}, {prepare_tabdef, 2}, {create_table, 1}, {delete_table, 1},
18      {add_table_copy, 3}, {add_table_index, 2}, {dirty_read, 2}, {read, 3},
19      {dirty_select, 2}, {select, 3}, {dirty_count_records, 2}, {count_records, 2},
20      {dirty_write, 2}, {write, 3}, {dirty_delete, 2}, {delete, 3},
21      {dirty_delete_object, 2}, {delete_object, 3}, {delete_where, 2},
22      {dirty_delete_where, 2}, {async_dirty, 2}, {sync_dirty, 2}, {transaction, 2}];
23 behaviour_info(_) -> undefined.
24 
25 -type({storage_host, {type, 49, binary, []}, []}).
26 
27 -type({storage_table, {type, 50, atom, []}, []}).
28 
29 -type({lock_kind,
30        {type, 51, union, [{atom, 51, read}, {atom, 51, write}, {atom, 51, sticky_write}]},
31        []}).
32 
33 -record(table, {host_name, backend, def}).
34 
35 -record(mnesia_def, {table, tabdef}).
36 
37 -include("ejabberd.hrl"). % This is used for ERROR_MSG
38 
39 %% Returns all hosts where the table Tab is defined
40 -'spec'({{all_table_hosts, 1},
41 	 [{type, 61, 'fun',
42 	   [{type, 61, product, [{type, 61, storage_table, []}]},
43 	    {type, 62, list, [{type, 62, storage_host, []}]}]}]}).
44 
45 all_table_hosts(Tab) ->
46     TT = setelement(2, {table, {<<"hidding_from_dialyzer">>, '$2'}, '_', '_'},
47 		    {'$1', '$2'}),
48     Res = (catch mnesia:dirty_select(table,
49 				     [{TT, [{'=:=', '$2', {const, Tab}}], ['$1']}])),
50     case Res of
51       Res when is_list(Res) -> [HostB || HostB <- Res, is_binary(HostB)];
52       _ -> []
53     end.
54 
55 -'spec'({{table_info, 3},
56 	 [{type, 75, 'fun',
57 	   [{type, 75, product,
58 	     [{type, 75, storage_host, []}, {type, 75, storage_table, []},
59 	      {type, 75, atom, []}]},
60 	    {type, 76, any, []}]}]}).
61 
62 table_info(Host, Tab, InfoKey) ->
63     Info = case get_table(Host, Tab) of
64 	     #table{backend = mnesia, def = #mnesia_def{tabdef = Def}} ->
65 		 [{backend, mnesia} | Def];
66 	     #table{backend = Backend, def = Def} ->
67 		 Info1 = Backend:table_info(Def),
68 		 BackendName = case Backend of gen_storage_odbc -> odbc end,
69 		 [{backend, BackendName} | Info1]
70 	   end,
71     case InfoKey of
72       all -> Info;
73       _ ->
74 	  case lists:keysearch(InfoKey, 1, Info) of
75 	    {value, {_, Value}} -> Value;
76 	    false when InfoKey =:= record_name -> Tab
77 	  end
78     end.
79 
80 %% @spec create_table(backend(), Host::binary(), Name::atom(), options()) -> {atomic, ok} | {aborted, Reason}
81 %% @type options() = [option()]
82 %% @type option() = {odbc_host, string()}
83 %%                | {Table::atom(), [tabdef()]}
84 %% @type tabdef() = {attributes, AtomList}
85 %%                | {record_name, atom()}
86 %%                | {types, attributedef()}
87 %% @type attributedef() = [{Column::atom(), columndef()}]
88 %% @type columndef() = text
89 %%                   | int
90 %%                   | tuple()
91 
92 
93 %% With an arbitrary number of columndef()
94 %% option() is any mnesia option
95 %% columndef() defaults to text for all unspecified attributes
96 
97 
98 -'spec'({{create_table, 4},
99 	 [{type, 118, 'fun',
100 	   [{type, 118, product,
101 	     [{type, 118, atom, []}, {type, 118, storage_host, []},
102 	      {type, 118, storage_table, []}, {type, 118, list, []}]},
103 	    {type, 119, tuple, any}]}]}).
104 
105 create_table(mnesia, Host, Tab, Def) ->
106     MDef = filter_mnesia_tabdef(Def),
107     define_table(mnesia, Host, Tab, #mnesia_def{table = Tab, tabdef = MDef}),
108     mnesia:create_table(Tab, MDef);
109 create_table(odbc, Host, Tab, Def) ->
110     ODef = gen_storage_odbc:prepare_tabdef(Tab, Def),
111     define_table(gen_storage_odbc, Host, Tab, ODef),
112     gen_storage_odbc:create_table(ODef).
113 
114 -'spec'({{define_table, 4},
115 	 [{type, 132, 'fun',
116 	   [{type, 132, product,
117 	     [{type, 132, atom, []}, {type, 132, storage_host, []},
118 	      {type, 132, storage_table, []},
119 	      {type, 132, union,
120 	       [{type, 132, record, [{atom, 132, mnesia_def}]},
121 		{type, 132, tuple, any}]}]},
122 	    {atom, 133, ok}]}]}).
123 
124 define_table(Backend, Host, Name, Def) ->
125     mnesia:create_table(table, [{attributes, record_info(fields, table)}]),
126     mnesia:dirty_write(#table{host_name = {Host, Name}, backend = Backend, def = Def}).
127 
128 %% @spec (list()) -> [{atom(), any()}]
129 
130 
131 -'spec'({{filter_mnesia_tabdef, 1},
132 	 [{type, 142, 'fun',
133 	   [{type, 142, product, [{type, 142, list, []}]},
134 	    {type, 143, list, [{type, 143, any, []}]}]}]}).
135 
136 filter_mnesia_tabdef(TabDef) -> lists:filter(fun filter_mnesia_tabdef_/1, TabDef).
137 
138 filter_mnesia_tabdef_({access_mode, _}) -> true;
139 filter_mnesia_tabdef_({attributes, _}) -> true;
140 filter_mnesia_tabdef_({disc_copies, _}) -> true;
141 filter_mnesia_tabdef_({disc_only_copies, _}) -> true;
142 filter_mnesia_tabdef_({index, _}) -> true;
143 filter_mnesia_tabdef_({load_order, _}) -> true;
144 filter_mnesia_tabdef_({ram_copies, _}) -> true;
145 filter_mnesia_tabdef_({record_name, _}) -> true;
146 filter_mnesia_tabdef_({snmp, _}) -> true;
147 filter_mnesia_tabdef_({type, _}) -> true;
148 filter_mnesia_tabdef_({local_content, _}) -> true;
149 filter_mnesia_tabdef_(_) -> false.
150 
151 -'spec'({{delete_table, 2},
152 	 [{type, 162, 'fun',
153 	   [{type, 162, product,
154 	     [{type, 162, storage_host, []}, {type, 162, storage_table, []}]},
155 	    {type, 163, tuple, [{atom, 163, atomic}, {atom, 163, ok}]}]}]}).
156 
157 delete_table(Host, Tab) -> backend_apply(delete_table, Host, Tab).
158 
159 -'spec'({{add_table_copy, 4},
160 	 [{type, 168, 'fun',
161 	   [{type, 168, product,
162 	     [{type, 168, storage_host, []}, {type, 168, storage_table, []},
163 	      {type, 168, node, []}, {type, 168, atom, []}]},
164 	    {type, 169, tuple, [{atom, 169, atomic}, {atom, 169, ok}]}]}]}).
165 
166 add_table_copy(Host, Tab, Node, Type) ->
167     backend_apply(add_table_copy, Host, Tab, [Node, Type]).
168 
169 -'spec'({{add_table_index, 3},
170 	 [{type, 173, 'fun',
171 	   [{type, 173, product,
172 	     [{type, 173, storage_host, []}, {type, 173, storage_table, []},
173 	      {type, 173, atom, []}]},
174 	    {type, 174, tuple, [{atom, 174, atomic}, {atom, 174, ok}]}]}]}).
175 
176 add_table_index(Host, Tab, Attribute) ->
177     backend_apply(add_table_index, Host, Tab, [Attribute]).
178 
179 -'spec'({{read, 2},
180 	 [{type, 179, 'fun',
181 	   [{type, 179, product,
182 	     [{type, 179, storage_host, []},
183 	      {type, 179, tuple,
184 	       [{type, 179, storage_table, []}, {type, 179, any, []}]}]},
185 	    {type, 180, list, [{type, 180, tuple, any}]}]}]}).
186 
187 read(Host, {Tab, Key}) -> backend_apply(read, Host, Tab, [Key, read]).
188 
189 -'spec'({{read, 4},
190 	 [{type, 184, 'fun',
191 	   [{type, 184, product,
192 	     [{type, 184, storage_host, []}, {type, 184, storage_table, []},
193 	      {type, 184, any, []}, {type, 184, lock_kind, []}]},
194 	    {type, 185, list, [{type, 185, tuple, any}]}]}]}).
195 
196 read(Host, Tab, Key, LockKind) -> backend_apply(read, Host, Tab, [Key, LockKind]).
197 
198 -'spec'({{dirty_read, 2},
199 	 [{type, 190, 'fun',
200 	   [{type, 190, product,
201 	     [{type, 190, storage_host, []},
202 	      {type, 190, tuple,
203 	       [{type, 190, storage_table, []}, {type, 190, any, []}]}]},
204 	    {type, 191, list, [{type, 191, tuple, any}]}]}]}).
205 
206 dirty_read(Host, {Tab, Key}) -> backend_apply(dirty_read, Host, Tab, [Key]).
207 
208 -'spec'({{dirty_read, 3},
209 	 [{type, 195, 'fun',
210 	   [{type, 195, product,
211 	     [{type, 195, storage_host, []}, {type, 195, storage_table, []},
212 	      {type, 195, any, []}]},
213 	    {type, 196, list, [{type, 196, tuple, any}]}]}]}).
214 
215 dirty_read(Host, Tab, Key) -> backend_apply(dirty_read, Host, Tab, [Key]).
216 
217 %% select/3
218 
219 
220 %% If Matchvalue is a tuple, then its size must be == length(string:tokens(Matchrule, "_"))
221 -type({matchvalue,
222        {type, 204, union,
223 	[{atom, 204, '_'}, {type, 205, integer, []}, {type, 206, string, []},
224 	 {type, 207, tuple, any}]},
225        []}).
226 
227 %%                    | {matchvalue(), matchrule()}.
228 -type({matchrule,
229        {type, 209, union,
230 	[{type, 209, tuple,
231 	  [{atom, 209, 'and'}, {type, 209, matchrule, []}, {type, 209, matchrule, []}]},
232 	 {type, 210, tuple,
233 	  [{atom, 210, 'andalso'}, {type, 210, matchrule, []},
234 	   {type, 210, matchrule, []}]},
235 	 {type, 211, tuple,
236 	  [{atom, 211, 'or'}, {type, 211, matchrule, []}, {type, 211, matchrule, []}]},
237 	 {type, 212, tuple,
238 	  [{atom, 212, 'orelse'}, {type, 212, matchrule, []},
239 	   {type, 212, matchrule, []}]},
240 	 {type, 213, tuple,
241 	  [{atom, 213, '='},
242 	   {ann_type, 213, [{var, 213, 'Attribute'}, {type, 213, atom, []}]},
243 	   {type, 213, matchvalue, []}]},
244 	 {type, 214, tuple,
245 	  [{atom, 214, '<'},
246 	   {ann_type, 214, [{var, 214, 'Attribute'}, {type, 214, atom, []}]},
247 	   {type, 214, matchvalue, []}]},
248 	 {type, 215, tuple,
249 	  [{atom, 215, '=/='},
250 	   {ann_type, 215, [{var, 215, 'Attribute'}, {type, 215, atom, []}]},
251 	   {type, 215, matchvalue, []}]},
252 	 {type, 216, tuple,
253 	  [{atom, 216, like},
254 	   {ann_type, 216, [{var, 216, 'Attribute'}, {type, 216, atom, []}]},
255 	   {type, 216, matchvalue, []}]}]},
256        []}).
257 
258 %% For the like operator the last element (not the tail as in
259 %% matchspecs) may be '_'.
260 -'spec'({{select, 3},
261 	 [{type, 220, 'fun',
262 	   [{type, 220, product,
263 	     [{type, 220, storage_host, []}, {type, 220, storage_table, []},
264 	      {type, 220, list, [{type, 220, matchrule, []}]}]},
265 	    {type, 221, list, [{type, 221, tuple, any}]}]}]}).
266 
267 select(Host, Tab, MatchRules) -> select(Host, Tab, MatchRules, read).
268 
269 -'spec'({{select, 4},
270 	 [{type, 225, 'fun',
271 	   [{type, 225, product,
272 	     [{type, 225, storage_host, []}, {type, 225, storage_table, []},
273 	      {type, 225, list, [{type, 225, matchrule, []}]},
274 	      {type, 225, lock_kind, []}]},
275 	    {type, 226, list, [{type, 226, tuple, any}]}]}]}).
276 
277 select(Host, Tab, MatchRules, Lock) ->
278     case get_table(Host, Tab) of
279       #table{backend = mnesia} ->
280 	  MatchSpec = matchrules_to_mnesia_matchspec(Tab, MatchRules),
281 	  mnesia:select(Tab, MatchSpec, Lock);
282       #table{backend = Backend, def = Def} -> Backend:select(Def, MatchRules, undefined)
283     end.
284 
285 -'spec'({{select, 5},
286 	 [{type, 237, 'fun',
287 	   [{type, 237, product,
288 	     [{type, 237, storage_host, []}, {type, 237, storage_table, []},
289 	      {type, 237, list, [{type, 237, matchrule, []}]}, {type, 237, integer, []},
290 	      {type, 237, lock_kind, []}]},
291 	    {type, 238, union,
292 	     [{type, 238, tuple,
293 	       [{type, 238, list, [{type, 238, tuple, any}]}, {type, 238, any, []}]},
294 	      {atom, 238, '$end_of_table'}]}]}]}).
295 
296 select(Host, Tab, MatchRules, N, Lock) ->
297     case get_table(Host, Tab) of
298       #table{backend = mnesia} ->
299 	  MatchSpec = matchrules_to_mnesia_matchspec(Tab, MatchRules),
300 	  mnesia:select(Tab, MatchSpec, N, Lock);
301       #table{backend = Backend, def = Def} -> Backend:select(Def, MatchRules, N)
302     end.
303 
304 -'spec'({{select, 2},
305 	 [{type, 249, 'fun',
306 	   [{type, 249, product,
307 	     [{type, 249, tuple,
308 	       [{type, 249, storage_host, []}, {type, 249, storage_table, []}]},
309 	      {type, 249, any, []}]},
310 	    {type, 250, union,
311 	     [{type, 250, tuple,
312 	       [{type, 250, list, [{type, 250, tuple, any}]}, {type, 250, any, []}]},
313 	      {atom, 250, '$end_of_table'}]}]}]}).
314 
315 select({Host, Tab}, Cont) ->
316     case get_table(Host, Tab) of
317       #table{backend = mnesia} -> mnesia:select(Cont);
318       #table{backend = Backend} -> Backend:select(Cont)
319     end.
320 
321 -'spec'({{dirty_select, 3},
322 	 [{type, 259, 'fun',
323 	   [{type, 259, product,
324 	     [{type, 259, storage_host, []}, {type, 259, storage_table, []},
325 	      {type, 259, list, [{type, 259, matchrule, []}]}]},
326 	    {type, 260, list, [{type, 260, tuple, any}]}]}]}).
327 
328 dirty_select(Host, Tab, MatchRules) ->
329     case get_table(Host, Tab) of
330       #table{backend = mnesia} ->
331 	  MatchSpec = matchrules_to_mnesia_matchspec(Tab, MatchRules),
332 	  mnesia:dirty_select(Tab, MatchSpec);
333       #table{backend = Backend, def = Def} -> Backend:dirty_select(Def, MatchRules)
334     end.
335 
336 matchrules_to_mnesia_matchspec(Tab, MatchRules) ->
337     RecordName = mnesia:table_info(Tab, record_name),
338     Attributes = mnesia:table_info(Tab, attributes),
339         %% Build up {record_name, '$1', '$2', ...}
340     MatchHead = list_to_tuple([RecordName | lists:reverse(lists:foldl(fun (_, L) ->
341 									      A =
342 										  list_to_atom([$$
343 												| integer_to_list(length(L)
344 														    +
345 														    1)]),
346 									      [A | L]
347 								      end,
348 								      [], Attributes))]),
349         %% Transform conditions
350     MatchConditions = [matchrules_transform_conditions(Attributes, Rule)
351 		       || Rule <- MatchRules],
352         %% Always full records
353     MatchBody = ['$_'],
354     [{MatchHead, MatchConditions, MatchBody}].
355 
356 %% TODO: special handling for '=='?
357 matchrules_transform_conditions(Attributes, {Op, Attribute, Value})
358     when Op =:= '=';
359 	 Op =:= '==';
360 	 Op =:= '=:=';
361 	 Op =:= like;
362 	 Op =:= '=/=';
363 	 Op =:= '<';
364 	 Op =:= '>';
365 	 Op =:= '>=';
366 	 Op =:= '=<' ->
367     Var = case list_find(Attribute, Attributes) of
368 	    false -> exit(unknown_attribute);
369 	    N -> list_to_atom([$$ | integer_to_list(N)])
370 	  end,
371     if is_tuple(Value) ->
372 	   {Expr, _} = lists:foldl(fun ('_', {R, N}) -> {R, N + 1};
373 				       (V, {R, N}) ->
374 					   {[matchrules_transform_column_op(Op,
375 									    {element, N,
376 									     Var},
377 									    {const, V})
378 					     | R],
379 					    N + 1}
380 				   end,
381 				   {[], 1}, tuple_to_list(Value)),
382 	   case Expr of
383 	     [E] -> E;
384 	     _ -> list_to_tuple(['andalso' | Expr])
385 	   end;
386        true -> matchrules_transform_column_op(Op, Var, Value)
387     end;
388 matchrules_transform_conditions(Attributes, T) when is_tuple(T) ->
389     L = tl(tuple_to_list(T)),
390     L2 = [matchrules_transform_conditions(Attributes, E) || E <- L],
391     list_to_tuple([element(1, T) | L2]).
392 
393 matchrules_transform_column_op(like, Expression, Pattern) ->
394     case lists:foldl(fun ('_', {R, E1}) -> {R, E1};
395 			 (P, {R, E1}) ->
396 			     Comparision = {'=:=', {hd, E1}, {const, P}},
397 			     {[Comparision | R], {tl, E1}}
398 		     end,
399 		     {[], Expression}, Pattern)
400 	of
401       {[Comparision], _} -> Comparision;
402       {Comparisions, _} -> list_to_tuple(['andalso' | lists:reverse(Comparisions)])
403     end;
404 matchrules_transform_column_op(Op, Expression, Pattern) when Op =:= '='; Op =:= '=:=' ->
405     {'=:=', Expression, Pattern};
406 matchrules_transform_column_op(Op, Expression, Pattern) -> {Op, Expression, Pattern}.
407 
408 %% Finds the first occurence of an element in a list
409 list_find(E, L) -> list_find(E, L, 1).
410 
411 list_find(_, [], _) -> false;
412 list_find(E, [E | _], N) -> N;
413 list_find(E, [_ | L], N) -> list_find(E, L, N + 1).
414 
415 -'spec'({{dirty_count_records, 2},
416 	 [{type, 363, 'fun',
417 	   [{type, 363, product,
418 	     [{type, 363, storage_host, []}, {type, 363, storage_table, []}]},
419 	    {type, 364, integer, []}]}]}).
420 
421 dirty_count_records(Host, Tab) -> dirty_count_records(Host, Tab, []).
422 
423 -'spec'({{dirty_count_records, 3},
424 	 [{type, 368, 'fun',
425 	   [{type, 368, product,
426 	     [{type, 368, storage_host, []}, {type, 368, storage_table, []},
427 	      {type, 368, list, [{type, 368, matchrule, []}]}]},
428 	    {type, 369, integer, []}]}]}).
429 
430 dirty_count_records(Host, Tab, MatchRules) ->
431     case get_table(Host, Tab) of
432       #table{backend = mnesia} ->
433 	  [{MatchHead, MatchConditions, _}] = matchrules_to_mnesia_matchspec(Tab,
434 									     MatchRules),
435 	  MatchSpec = [{MatchHead, MatchConditions, [[]]}],
436 	  length(mnesia:dirty_select(Tab, MatchSpec));
437       #table{backend = Backend, def = Def} -> Backend:dirty_count_records(Def, MatchRules)
438     end.
439 
440 -define(COUNT_RECORDS_BATCHSIZE, 100).
441 
442 -'spec'({{count_records, 2},
443 	 [{type, 383, 'fun',
444 	   [{type, 383, product,
445 	     [{type, 383, storage_host, []}, {type, 383, storage_table, []}]},
446 	    {type, 384, integer, []}]}]}).
447 
448 count_records(Host, Tab) -> count_records(Host, Tab, []).
449 
450 -'spec'({{count_records, 3},
451 	 [{type, 388, 'fun',
452 	   [{type, 388, product,
453 	     [{type, 388, storage_host, []}, {type, 388, storage_table, []},
454 	      {type, 388, list, [{type, 388, matchrule, []}]}]},
455 	    {type, 389, integer, []}]}]}).
456 
457 count_records(Host, Tab, MatchRules) ->
458     case get_table(Host, Tab) of
459       #table{backend = mnesia} ->
460 	  [{MatchHead, MatchConditions, _}] = matchrules_to_mnesia_matchspec(Tab,
461 									     MatchRules),
462 	  MatchSpec = [{MatchHead, MatchConditions, [[]]}],
463 	  case mnesia:select(Tab, MatchSpec, ?COUNT_RECORDS_BATCHSIZE, read) of
464 	    {Result, Cont} ->
465 		Count = length(Result), mnesia_count_records_cont(Cont, Count);
466 	    '$end_of_table' -> 0
467 	  end;
468       #table{backend = Backend, def = Def} -> Backend:count_records(Def, MatchRules)
469     end.
470 
471 mnesia_count_records_cont(Cont, Count) ->
472     case mnesia:select(Cont) of
473       {Result, Cont} ->
474 	  NewCount = Count + length(Result), mnesia_count_records_cont(Cont, NewCount);
475       '$end_of_table' -> Count
476     end.
477 
478 -'spec'({{write, 2},
479 	 [{type, 418, 'fun',
480 	   [{type, 418, product,
481 	     [{type, 418, storage_host, []}, {type, 418, tuple, any}]},
482 	    {atom, 419, ok}]}]}).
483 
484 write(Host, Rec) -> Tab = element(1, Rec), backend_apply(write, Host, Tab, [Rec, write]).
485 
486 -'spec'({{write, 4},
487 	 [{type, 424, 'fun',
488 	   [{type, 424, product,
489 	     [{type, 424, storage_host, []}, {type, 424, storage_table, []},
490 	      {type, 424, tuple, any}, {type, 424, lock_kind, []}]},
491 	    {atom, 425, ok}]}]}).
492 
493 write(Host, Tab, Rec, LockKind) -> backend_apply(write, Host, Tab, [Rec, LockKind]).
494 
495 -'spec'({{dirty_write, 2},
496 	 [{type, 430, 'fun',
497 	   [{type, 430, product,
498 	     [{type, 430, storage_host, []}, {type, 430, tuple, any}]},
499 	    {atom, 431, ok}]}]}).
500 
501 dirty_write(Host, Rec) ->
502     Tab = element(1, Rec), backend_apply(dirty_write, Host, Tab, [Rec]).
503 
504 -'spec'({{dirty_write, 3},
505 	 [{type, 436, 'fun',
506 	   [{type, 436, product,
507 	     [{type, 436, storage_host, []}, {type, 436, storage_table, []},
508 	      {type, 436, tuple, any}]},
509 	    {atom, 437, ok}]}]}).
510 
511 dirty_write(Host, Tab, Rec) -> backend_apply(dirty_write, Host, Tab, [Rec]).
512 
513 -'spec'({{delete, 2},
514 	 [{type, 442, 'fun',
515 	   [{type, 442, product,
516 	     [{type, 442, storage_host, []},
517 	      {type, 442, tuple,
518 	       [{type, 442, storage_table, []}, {type, 442, any, []}]}]},
519 	    {atom, 443, ok}]}]}).
520 
521 delete(Host, {Tab, Key}) -> backend_apply(delete, Host, Tab, [Key, write]).
522 
523 -'spec'({{delete, 4},
524 	 [{type, 447, 'fun',
525 	   [{type, 447, product,
526 	     [{type, 447, storage_host, []}, {type, 447, storage_table, []},
527 	      {type, 447, any, []}, {type, 447, lock_kind, []}]},
528 	    {atom, 448, ok}]}]}).
529 
530 delete(Host, Tab, Key, LockKind) -> backend_apply(delete, Host, Tab, [Key, LockKind]).
531 
532 -'spec'({{dirty_delete, 2},
533 	 [{type, 453, 'fun',
534 	   [{type, 453, product,
535 	     [{type, 453, storage_host, []},
536 	      {type, 453, tuple,
537 	       [{type, 453, storage_table, []}, {type, 453, any, []}]}]},
538 	    {atom, 454, ok}]}]}).
539 
540 dirty_delete(Host, {Tab, Key}) -> backend_apply(dirty_delete, Host, Tab, [Key]).
541 
542 -'spec'({{dirty_delete, 3},
543 	 [{type, 458, 'fun',
544 	   [{type, 458, product,
545 	     [{type, 458, storage_host, []}, {type, 458, storage_table, []},
546 	      {type, 458, any, []}]},
547 	    {atom, 459, ok}]}]}).
548 
549 dirty_delete(Host, Tab, Key) -> backend_apply(dirty_delete, Host, Tab, [Key]).
550 
551 -'spec'({{delete_object, 2},
552 	 [{type, 464, 'fun',
553 	   [{type, 464, product,
554 	     [{type, 464, storage_host, []}, {type, 464, tuple, any}]},
555 	    {atom, 465, ok}]}]}).
556 
557 delete_object(Host, Rec) ->
558     Tab = element(1, Rec), backend_apply(delete_object, Host, Tab, [Rec, write]).
559 
560 -'spec'({{delete_object, 4},
561 	 [{type, 470, 'fun',
562 	   [{type, 470, product,
563 	     [{type, 470, storage_host, []}, {type, 470, storage_table, []},
564 	      {type, 470, tuple, any}, {type, 470, lock_kind, []}]},
565 	    {atom, 471, ok}]}]}).
566 
567 delete_object(Host, Tab, Rec, LockKind) ->
568     backend_apply(delete_object, Host, Tab, [Rec, LockKind]).
569 
570 -'spec'({{dirty_delete_object, 2},
571 	 [{type, 476, 'fun',
572 	   [{type, 476, product,
573 	     [{type, 476, storage_host, []}, {type, 476, tuple, any}]},
574 	    {atom, 477, ok}]}]}).
575 
576 dirty_delete_object(Host, Rec) ->
577     Tab = element(1, Rec), backend_apply(delete_object, Host, Tab, [Rec]).
578 
579 -'spec'({{dirty_delete_object, 3},
580 	 [{type, 482, 'fun',
581 	   [{type, 482, product,
582 	     [{type, 482, storage_host, []}, {type, 482, storage_table, []},
583 	      {type, 482, tuple, any}]},
584 	    {atom, 483, ok}]}]}).
585 
586 dirty_delete_object(Host, Tab, Rec) -> backend_apply(delete_object, Host, Tab, [Rec]).
587 
588 -define(DELETE_WHERE_BATCH_SIZE, 100).
589 
590 -'spec'({{delete_where, 3},
591 	 [{type, 490, 'fun',
592 	   [{type, 490, product,
593 	     [{type, 490, storage_host, []}, {type, 490, storage_table, []},
594 	      {type, 490, list, [{type, 490, matchrule, []}]}]},
595 	    {atom, 491, ok}]}]}).
596 
597 delete_where(Host, Tab, MatchRules) ->
598     case get_table(Host, Tab) of
599       #table{backend = mnesia} ->
600 	  MatchSpec = matchrules_to_mnesia_matchspec(Tab, MatchRules),
601 	  mnesia:write_lock_table(Tab),
602 	  SR = mnesia:select(Tab, MatchSpec, ?DELETE_WHERE_BATCH_SIZE, write),
603 	  delete_where_mnesia1(SR);
604       #table{backend = Backend, def = Def} -> Backend:delete_where(Def, MatchRules)
605     end.
606 
607 delete_where_mnesia1('$end_of_table') -> ok;
608 delete_where_mnesia1({Objects, Cont}) ->
609     lists:foreach(fun (Object) -> mnesia:delete_object(Object) end, Objects),
610     delete_where_mnesia1(mnesia:select(Cont)).
611 
612 -'spec'({{dirty_delete_where, 3},
613 	 [{type, 513, 'fun',
614 	   [{type, 513, product,
615 	     [{type, 513, storage_host, []}, {type, 513, storage_table, []},
616 	      {type, 513, list, [{type, 513, matchrule, []}]}]},
617 	    {atom, 514, ok}]}]}).
618 
619 dirty_delete_where(Host, Tab, MatchRules) ->
620     case get_table(Host, Tab) of
621       #table{backend = mnesia} ->
622 	  MatchSpec = matchrules_to_mnesia_matchspec(Tab, MatchRules),
623 	  F = fun () ->
624 		      mnesia:write_lock_table(Tab),
625 		      Objects = mnesia:select(Tab, MatchSpec, write),
626 		      lists:foreach(fun (Object) -> mnesia:delete_object(Object) end,
627 				    Objects)
628 	      end,
629 	  {atomic, _} = mnesia:transaction(F);
630       #table{backend = Backend, def = Def} -> Backend:dirty_delete_where(Def, MatchRules)
631     end.
632 
633 -'spec'({{write_lock_table, 2},
634 	 [{type, 533, 'fun',
635 	   [{type, 533, product,
636 	     [{type, 533, storage_host, []}, {type, 533, storage_table, []}]},
637 	    {atom, 534, ok}]}]}).
638 
639 write_lock_table(Host, Tab) ->
640     case get_table(Host, Tab) of
641       #table{backend = mnesia} -> mnesia:write_lock_table(Tab);
642       _ -> ignored
643     end.
644 
645 -'spec'({{transaction, 3},
646 	 [{type, 544, 'fun',
647 	   [{type, 544, product,
648 	     [{type, 544, storage_host, []}, {type, 544, storage_table, []},
649 	      {type, 544, 'fun', []}]},
650 	    {type, 545, union,
651 	     [{type, 545, tuple, [{atom, 545, atomic}, {type, 545, any, []}]},
652 	      {type, 545, tuple, [{atom, 545, aborted}, {type, 545, string, []}]}]}]}]}).
653 
654 %% Warning: all tabs touched by the transaction must use the same
655 %% storage backend!
656 transaction(Host, Tab, Fun) ->
657         %% This is just to ensure an error is logged when error appears:
658     case transaction2(Host, Tab, Fun) of
659       {atomic, _} = Good -> Good;
660       {aborted, Reason} = Bad ->
661 	  Stacktrace = erlang:get_stacktrace(),
662 	  ?ERROR_MSG("Transaction failed for host ~p in tab ~p with fun ~p:~nReason: ~p~nStacktrace: ~p",
663 		     [Host, Tab, Fun, Reason, Stacktrace]),
664 	  Bad
665     end.
666 
667 transaction2(Host, Tab, Fun) ->
668     case get_table(Host, Tab) of
669       #table{backend = mnesia} -> mnesia:transaction(Fun);
670       #table{backend = Backend, def = Def} -> Backend:transaction(Def, Fun)
671     end.
672 
673 -'spec'({{sync_dirty, 3},
674 	 [{type, 569, 'fun',
675 	   [{type, 569, product,
676 	     [{type, 569, storage_host, []}, {type, 569, storage_table, []},
677 	      {type, 569, 'fun', []}]},
678 	    {type, 570, tuple, [{atom, 570, atomic}, {type, 570, any, []}]}]}]}).
679 
680 %% Warning: all tabs touched by the sync_dirty must use the same
681 %% storage backend!
682 sync_dirty(Host, Tab, Fun) ->
683     case get_table(Host, Tab) of
684       #table{backend = mnesia} -> mnesia:sync_dirty(Fun);
685       #table{backend = Backend, def = Def} -> Backend:sync_dirty(Def, Fun)
686     end.
687 
688 -'spec'({{async_dirty, 3},
689 	 [{type, 582, 'fun',
690 	   [{type, 582, product,
691 	     [{type, 582, storage_host, []}, {type, 582, storage_table, []},
692 	      {type, 582, 'fun', []}]},
693 	    {type, 583, tuple, [{atom, 583, atomic}, {type, 583, any, []}]}]}]}).
694 
695 %% Warning: all tabs touched by the async_dirty must use the same
696 %% storage backend!
697 async_dirty(Host, Tab, Fun) ->
698     case get_table(Host, Tab) of
699       #table{backend = mnesia} -> mnesia:async_dirty(Fun);
700       #table{backend = Backend, def = Def} -> Backend:async_dirty(Def, Fun)
701     end.
702 
703 %% TODO: fix the calling code so this function clause isn't needed
704 get_table(Host, Tab) when is_list(Host) -> get_table(list_to_binary(Host), Tab);
705 get_table(Host, Tab) ->
706     case {mnesia:dirty_read(table, {Host, Tab}), Host} of
707       {[T], _} -> T;
708       {_, Host} when is_binary(Host) ->
709 	  Prefix = lists:nth(1, string:tokens(binary_to_list(Host), ".")),
710 	  get_table({global, Prefix}, Tab);
711       {_, {global, _Prefix}} -> get_table(global, Tab);
712       {_, global} ->
713 	  catch throw(error123),
714 	  Stacktrace = erlang:get_stacktrace(),
715 	  error_logger:error_msg("gen_storage: Table ~p not found on ~p~nStacktrace: ~p",
716 				 [Tab, Host, Stacktrace]),
717 	  exit(table_not_found)
718     end.
719 
720 backend_apply(F, Host, Tab) -> backend_apply(F, Host, Tab, []).
721 
722 backend_apply(F, Host, Tab, A) ->
723     #table{backend = Backend, def = Def} = get_table(Host, Tab),
724     case Def of
725       #mnesia_def{table = Tab} -> apply(Backend, F, [Tab | A]);
726       _ -> apply(Backend, F, [Def | A])
727     end.