PageRenderTime 26ms CodeModel.GetById 13ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/ce/src/ce_db.erl

http://github.com/gebi/jungerl
Erlang | 274 lines | 140 code | 38 blank | 96 comment | 0 complexity | e2c044d8005148c7f0e0d398f300f964 MD5 | raw file
  1%%% BEGIN ce_db.erl %%%
  2%%%
  3%%% ce - Miscellaneous Programming Support Libraries for Erlang/OTP
  4%%% Copyright (c)2003 Cat's Eye Technologies.  All rights reserved.
  5%%%
  6%%% Redistribution and use in source and binary forms, with or without
  7%%% modification, are permitted provided that the following conditions
  8%%% are met:
  9%%%
 10%%%   Redistributions of source code must retain the above copyright
 11%%%   notice, this list of conditions and the following disclaimer.
 12%%%
 13%%%   Redistributions in binary form must reproduce the above copyright
 14%%%   notice, this list of conditions and the following disclaimer in
 15%%%   the documentation and/or other materials provided with the
 16%%%   distribution.
 17%%%
 18%%%   Neither the name of Cat's Eye Technologies nor the names of its
 19%%%   contributors may be used to endorse or promote products derived
 20%%%   from this software without specific prior written permission.
 21%%%
 22%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 23%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 24%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 25%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 26%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 27%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 28%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 29%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 30%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 31%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 32%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 33%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 34%%% POSSIBILITY OF SUCH DAMAGE. 
 35
 36%% @doc Database library.
 37%%
 38%% <p>This library complements mnesia.  It uses mnesia to access the
 39%% databases, using a simpler (but less efficient) interface.</p>
 40%%
 41%% @end
 42
 43-module(ce_db).
 44-vsn('JUNGERL').
 45-author('catseye@catseye.mb.ca').
 46-copyright('Copyright (c)2003 Cat`s Eye Technologies. All rights reserved.').
 47
 48-export([create/1, connect/3]).
 49-export([probe_db_nodes/1]).
 50-export([read/2, read/3, write/2, delete/2]).
 51-export([foreach/2, fold/2, fold/3, all_records/1]).
 52-export([update_counter/2]).
 53-export([export/3, import/3]).
 54
 55%%% DB MANAGEMENT %%%
 56
 57%% @spec create(Tables::[tabledef()]) -> ok | {error, Reason}
 58%%         tabledef() = {table(), [option()]}
 59%% @doc Creates a schema with the given tables if one does not already exist.
 60%% This schema is created on the local node only.
 61%% If a schema already exists on the local node,
 62%% the given tables are added to it.  See the <code>mnesia</code> documentation
 63%% for an explanation of the <code>tabledef()</code> structure.
 64
 65create(Tables) ->
 66  mnesia:stop(),
 67  Node = node(),
 68  (catch mnesia:create_schema([Node])),
 69  mnesia:start(),
 70  lists:foreach(fun({Table, Properties}) ->
 71    (catch mnesia:create_table(Table, Properties))
 72  end, Tables),
 73  ok.
 74
 75%% @spec connect(Master::node(), RamTables::[table()], DiscTables::[table()]) -> ok | {error, Reason}
 76%% @doc Connects to a database hosted on a remote node.
 77%% This is useful for connecting slave nodes to a master after the
 78%% database has been created on the master node using <code>ce_db:create/1</code>.
 79%% This connection needs only be done once, at install time, not each time
 80%% the application is started.  If the connection is successful, local copies of
 81%% the given tables from the master node will be made in RAM and/or on
 82%% disc on the local node.
 83
 84connect(MasterNode, RamTables, DiscTables) ->
 85  mnesia:start(),
 86  Node = node(),
 87  spawn(MasterNode, ?MODULE, probe_db_nodes, [self()]),
 88  receive
 89    {probe_db_nodes_reply, DBNodes} ->
 90      mnesia:change_config(extra_db_nodes, DBNodes),
 91      mnesia:change_table_copy_type(schema, Node, disc_copies),
 92      lists:foreach(fun(X) ->
 93                      mnesia:add_table_copy(X, Node, ram_copies)
 94		    end, RamTables),
 95      lists:foreach(fun(X) ->
 96                      mnesia:add_table_copy(X, Node, disc_copies)
 97		    end, DiscTables),
 98      ok;
 99    Else ->
100      {error, Else}
101  end.
102
103%% @spec probe_db_nodes(pid()) -> ok
104%% @doc Used by <code>connect/3</code> to probe the remote node.
105%% This function is invoked on the remote (master) node, and sends a message
106%% back to the local (slave) node.
107
108probe_db_nodes(ForPid) ->
109  ForPid ! {probe_db_nodes_reply, mnesia:system_info(db_nodes)},
110  ok.
111
112%% @spec read(table(), key()) -> record() | nil
113%%         table() = atom()
114%%         key() = term()
115%% @doc Reads the record with a matching primary key from the table.
116%% Returns nil if no record was found.
117
118read(Table, Key) ->
119  case mnesia:transaction(fun() ->
120                            mnesia:read(Table, Key, read)
121			  end) of
122    {atomic, [H | T]} -> H;
123                    _ -> nil
124  end.
125
126%% @spec read(table(), index(), key()) -> [record()]
127%%         index() = atom()
128%% @doc Reads all records with a matching secondary key from the table.
129%% Returns an empty list if none were found.
130
131read(Table, Index, Key) ->
132  case mnesia:transaction(fun() ->
133                            mnesia:index_read(Table, Key, Index)
134			  end) of
135    {atomic, List} -> List;
136                 _ -> []
137  end.
138
139%% @spec write(table(), record()) -> true | false
140%% @doc Inserts or updates a single record in a data table.
141
142write(Table, Object) ->
143  case mnesia:transaction(fun() ->
144                            mnesia:write(Table, Object, write)
145			  end) of
146    {atomic, ok} -> true;
147	    Else -> false
148  end.
149
150%% @spec delete(table(), key()) -> true | false
151%% @doc Deletes a single record from a data table.
152
153delete(Table, Key) ->
154  case mnesia:transaction(fun() ->
155                            mnesia:delete(Table, Key, write)
156			  end) of
157    {atomic, ok} -> true;
158	    Else -> false
159  end.
160
161%% @spec foreach(table(), fun()) -> true | false
162%% @doc Traverses an entire table, calling fun/2 for each record.
163%% The result of the evaluation of the fun is discarded.
164
165foreach(Table, Fun) ->
166  case mnesia:transaction(fun() ->
167                            mnesia:foldl(Fun, [], Table), ok
168			  end) of
169    {atomic, ok} -> true;
170            Else -> false
171  end.
172
173%% @spec fold(table(), fun()) -> {ok, term()} | {error, Reason}
174%% @equiv fold(Table, Fun, [])
175
176fold(Table, Fun) ->
177  fold(Table, Fun, []).
178
179%% @spec fold(table(), fun(), term()) -> {ok, term()} | {error, Reason}
180%% @doc Folds an entire table, calling fun/2 for each record.
181
182fold(Table, Fun, Acc) ->
183  case mnesia:transaction(fun() ->
184                            mnesia:foldl(Fun, Acc, Table)
185			  end) of
186    {atomic, Acc0} -> {ok, Acc0};
187              Else -> Else
188  end.
189
190%% @spec all_records(table()) -> [record()]
191%% @doc Returns a list of all the records in the table.
192%% Complements mnesia:all_keys/1.  Note that this is not a particularly
193%% scaleable solution.
194
195all_records(Table) ->
196  Arity = mnesia:table_info(Table, arity),
197  Template = list_to_tuple([Table | lists:duplicate(Arity - 1, '_')]),
198  case mnesia:transaction(fun() ->
199                            mnesia:match_object(Template)
200			  end) of
201    {atomic, List} -> List;
202              Else -> []
203  end.
204
205%% @spec update_counter(table(), key()) -> {ok, integer()} | {error, Reason}
206%% @doc Increments an integer value in a table.
207%% Complements mnesia:dirty_update_counter/2.  This function is much less
208%% efficient, but it is cleaner.
209
210update_counter(Table, Key) ->
211  case mnesia:transaction(fun() ->
212                            [{T,K,Number}] = mnesia:read(Table, Key, read),
213                            mnesia:write(Table, {T,K,Number + 1}, write),
214			    Number + 1
215			  end) of
216    {atomic, N} -> {ok, N};
217           Else -> Else
218  end.
219
220%% @spec export(io_device(), table(), FieldNames::[atom()]) -> ok | {error, Reason}
221%% @doc Exports a table to a disk file. For simplicity,
222%% FieldNames should be the output of <code>record_info(fields, Table)</code>
223
224export(IoDevice, Table, FieldNames) ->
225  io:fwrite(IoDevice, "~p.~n", [Table]),
226  fold(Table, fun(Record, ok) ->
227    io:fwrite(IoDevice, "~p.~n", [map_fields(Record, FieldNames)])
228  end, ok),
229  io:fwrite(IoDevice, "~p.~n", [end_of_records]).
230
231map_fields(Record, FieldNames) ->
232  map_fields(tl(tuple_to_list(Record)), FieldNames, []).
233
234map_fields([], [], Acc) -> Acc;
235map_fields([Field | Fields], [FieldName | FieldNames], Acc) ->
236  map_fields(Fields, FieldNames, [{FieldName, Field} | Acc]).
237
238%% @spec import(io_device(), table(), FieldNames::[atom()]) -> ok | {error, Reason}
239%% @doc Imports a disk file into a table. For simplicity,
240%% FieldNames should be the output of <code>record_info(fields, Table)</code>
241
242import(IoDevice, Table, FieldNames) ->
243  case io:read(IoDevice, '') of
244    {ok, Table} ->
245      import_loop(IoDevice, Table, FieldNames);
246    _ ->
247      {error, could_not_read_table_tag}
248  end.
249
250import_loop(IoDevice, Table, FieldNames) ->
251  case io:read(IoDevice, '') of
252    {ok, end_of_records} ->
253      ok;
254    {ok, TupleList} ->
255      Record = map_record(Table, TupleList, FieldNames),
256      write(Table, Record),
257      import(IoDevice, Table, FieldNames)
258  end.
259
260map_record(Table, TupleList, FieldNames) ->
261  list_to_tuple([Table | map_record0(TupleList, FieldNames, [])]).
262
263map_record0(TupleList, [], Acc) ->
264  lists:reverse(Acc);
265map_record0(TupleList, [FieldName | FieldNames], Acc) ->
266  case lists:keysearch(FieldName, 1, TupleList) of
267    {value, {FieldName, FieldValue}} ->
268      map_record0(TupleList, FieldNames, [FieldValue | Acc]);
269    error ->
270      % should fill in the default value, will fill in atom 'default'
271      map_record0(TupleList, FieldNames, [default | Acc])
272  end.
273
274%%% END of ce_db.erl %%%