views:

63

answers:

3

I am trying to have a function that ensures the table I need is already created and if not to create it. Here's the sample:

ensure_table_exists(Table, MnesiaTables, Nodes) ->
case lists:member(Table, MnesiaTables) of
    true ->
        throw({error, db_might_have_already_been_created});
    false ->
        mnesia:create_table(Table, [{disc_copies, Nodes},
                {attributes, record_info(fields, Table)}]), 
        ok  
end.

The issue is that when compiling I get the error: illegal record info. It might have to do that record_info is resolved at compile time or that the second argument to record info should actually be a record that can be retrieved from the source code ?

A: 

You might want to have a look to Ulf Wiger's exprecs.

Reading from the description:

The module is a parse transform allowing you to export records. The transform adds accessor functions for instantiating, inspecting and modifying records, without having to introduce compile-time dependencies between modules.

Said that, your function sounds a bit defensive to me. The following page explains why it's a bad habit to program defensively in Erlang:

http://www.erlang.se/doc/programming_rules.shtml#HDR11

Roberto Aloi
I know about the bad habit of programming defensively in Erlang, but I don't understand what this has to do with my function ? I am only trying to make sure that I didn't created the schema already.
hyperboreean
+2  A: 

Yes, all record related things including record_info/2 are resolved at compile time. This means that record and field names must be known at compile time. This is the reason for the compiler error.

I don't think your function is really too defensive in that what you are doing is signaling a more specific error. It would be another matter if you were to return {error, ...}.

One last point is that if you mean to raise an exception you should not use throw/1 but instead use erlang:error/1. throw is intended for non-local return (caught with a catch) while erlang:error is intended for raising an exception. In many cases the result may be the same but the actual error value may be misleading (nocatch). It is always better the clearer you can show your intention, which in this case is signaling an error.

P.S. Yes, I know that catch also catches errors/exits as well. This was intentional. In a perfect world maybe catch should only catch throws and try only errors/exits.

rvirding
A: 

Unfortunately record_info is not really a function even if it looks like one.

You can verify that by testing the following. Create a file:

 -module(something).
 -record(a, {}).

Start the Erlang shell:

 > rr(something).
 [a]
 > record_info(fields, a).
 []
 > A = a.
 > record_info(fields, A).
 * 2: illegal record info

So my recommendation would be to either use a macro or a specialised function for the record_info part.

To answer your original question. Use something like:

 tables() ->
   [?TABLE_MACRO(tablename),
    ?TABLE_MACRO(tablename2),
    ...].

where TABLE_MACRO is something like:

 -define(TABLE_MACRO(Table), fun() ->
      mnesia:create_table(Table, [{disc_copies, Nodes},
               {attributes, record_info(fields, Table)}])
      end).

and then have a function using something like the below.

 [case CreateTable of
   {aborted, {already_exists, _}} -> ok;
   {atomic, ok} -> ok
  end || CreateTable <- tables()].

Yuck! Can be cleaned up quite a bit, but hopefully you understand the general idea.

  • Macro instead of variable use.
  • Match on both {atomic, ok} and {aborted, {already_exists, _TableName}}
Daniel Luna