Wednesday, April 20, 2011

What's the best way to prevent adding a record whose primary key is already present in mnesia?

Suppose I've got a simple record definition:

-record(data, {primary_key = '_', more_stuff = '_'}).

I want a simple function that adds one of these records to a mnesia database. But I want it to fail if there's already an entry with the same primary key.

(In the following examples, assume I've already defined

db_get_data(Key)->
    Q = qlc:q([Datum
               || Datum = #data{primary_key = RecordKey}
                      <- mnesia:table(data),
                  RecordKey =:= Key]),
    qlc:e(Q).

)

The following works, but strikes me as sort of ugly ...

add_data(D) when is_record(D, data)->
    {atomic, Result} = mnesia:transaction(fun()->
                                                  case db_get_data(D#data.primary_key) of
                                                      [] -> db_add_data(D);
                                                      _ -> {error, bzzt_duplicate_primary_key}
                                                  end
                                          end),

    case Result of
        {error, _} = Error -> throw(Error);
        _ -> result
    end.

This works too, but is also ugly:

add_data(D) when is_record(D, data)->
    {atomic, Result} = mnesia:transaction(fun()->
                                                  case db_get_data(D#data.primary_key) of
                                                      [] -> db_add_data(D);
                                                      _ -> throw({error, bzzt_duplicate_primary_key})
                                                  end
                                          end).

It differs from the above in that the above throws

{error, bzzt_duplicate_primary_key},

whereas this one throws

{error, {badmatch, {aborted, {throw,{error, bzzt_duplicate_primary_key}}}}}

So: is there some convention for indicating this sort of error? Or is there a built-in way that I can get mnesia to throw this error for me?

From stackoverflow
  • I think both of them are fine, if you only make your code more pretty, like:

    add_data(D) when is_record(D, data)->
    
        Fun = fun() ->
                      case db_get_data(D#data.primary_key) of
                          [] -> db_add_data(D);
                          _  -> throw({error, bzzt_duplicate_primary_key})
                      end
              end,
    
        {atomic, Result} = mnesia:activity(transaction, Fun).
    

    or

    add_data(D) when is_record(D, data)->
    
        Fun = fun() ->
                      case db_get_data(D#data.primary_key) of
                          [] -> db_add_data(D);
                          _  -> {error, bzzt_duplicate_primary_key}
                      end
              end,
    
        {atomic, Result} = mnesia:activity(transaction, Fun),
    
        case Result of
            {error, Error} -> throw(Error);
            _              -> result
        end.
    

    Do you throw errors or return errors? I would return an error myself. We split out code out into mnesia work units - a module with a set of functions that perform basic mnesia activities not in transactions, and an api module which 'composes' the work units into mnesia transactions with functions that look very similar to the one above.

    Jeremy Wall : Erlang is the one language where I think returning errors actually works. Pattern matching and Fail Fast actually help so that returning errors does what you mean for it to, instead of getting lost somewhere up the line.
    Gordon Guthrie : I agree personally - we tend only to throw errors on parsing user input or other circumstances where a throw represents a 'come back to here from many different places' and we will sort out...

0 comments:

Post a Comment