How to display mnesia records with corresponding field names?

Hi,

I am new to Erlang. I am writing escript which makes database related operations. I list the records by using mnesia:foldl method as follows;

select_all(RemoteNode, TableName) ->
  %% enumerate the records in a specific table
  rpc:call( RemoteNode, mnesia, transaction, [
    fun() -> mnesia:foldl(
      fun(Rec,_Acc) -> io:format("~p\n",[Rec]) end, 
      [], 
      TableName)
    end
  ]).

But this method returns list in tuple format. I want to listen records with corresponding field names.

After exploring the approach, I only find the following ;

-module('foo').
-author('Mats Cronqvist'). 
-export([go/0]). 
-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))). 
-record(r,{bla,foo,baz,bar}). 

go() -> ?rec_info(r,#r{}).`

> Result -> foo:go().
> [{bla,undefined},
> {foo,undefined},
> {baz,undefined},
> {bar,undefined}]

I couldn’t understand the syntax of the method stated above. So I couldn’t manage to apply it to my script.
How can I display the records with field names ?

Thanks in advance,

If I had to figure out Mnesia I’d most likely start here:

learn you some Erlang: Mnesia And The Art of Remembering

which is also a great all around Erlang resource.

You can find the attributes (aka fields) of a table using record_info(fields, TableOrRecordName) which returns a list.

When you read from mnesia you get a tuple representation of the record. Hence the tuple_to_list(R).

However this representation includes the name of the record as the first element, we call this a named tuple. tl(tuple_to_list(R)) drops this name leaving you with just the attributes of the table.

In your specific case

fun() -> mnesia:foldl(
  fun(Rec, _Acc) -> io:format(
    "~p\n", 
    [lists:zip(record_info(fields, TableName, tl(tuple_to_list(Rec)) ))]
  ) end,
  [], TableName) 
end
2 Likes

Firstly,

Thanks for your response. There is one more question I want to ask… Is it possible to retrieve “fields of given mnesia table” without having to define record ? I mean, I just wanna send table name as parameter, and see the list of fields of mnesia table.

Thanks in advance,

:mnesia.table_info(table_name, :attributes) will get you the fields defined for the particular table.

Hi,

Thanks for your response, it just worked. So, I can successfully get the fields of tables at runtime with the following function.

get_field_names(Rnode, TableName)->

rpc:call( Rnode, mnesia, transaction, [
fun()->io:format(“Fields: ~p~n”,[mnesia:table_info(TableName, attributes)])
end
] ).

My goal is to create or update record at runtime programatically. Is there a setter definition which sets attributes of record ?

Is your question about adding/removing columns on a table ?

See mnesia:transform_table/4

No,
I want to display all table rows with field names dynamically. In this context, according to the given table name, I want to create/update a record with attributes ( dynamically fetched as stated in the previous reply ) at runtime.

Look at mnesia:write and mnesia:read

Follow the tutorial Mnesia User Guide

The Learn You Some Erlang Mnesia chapter posted earlier is great too.

Hi,
Sorry, I couldn’t understand what you mean exactly :frowning: I just want to display table data with field names dynamically. So, I display data with the following function;

select_all(Rnode, T) ->
    %% enumerate the records in a specific table
    rpc:call( Rnode, mnesia, transaction, [
        fun()->mnesia:foldl(
            fun(Rec,_Acc)->io:format("~p\n",[?rec_info(**myRecordName**,Rec)]) end, [], T)
        end
   ] ).

When the table changes, the attribute list changes accordingly.
So, at run time, I should change the record definition; attribute list of it.

What I want to do is defining a template record with an empty attribute list as follows;

-record(myRecordName, {}).

According to the table name provided by user, I want to update the attribute list of record.
Is it possible in Erlang ?

In which case just follow @cmkarlsson suggestion of using mnesia:table_info(TableName, attributes)

If I understand you correctly you want to define an erlang record at runtime? This is not possible as erlang records are simply a compile time construct. Underneath they are in fact just tuples of the form {name, field1, field2, field3}.

There is no way to create or modify an erlang record outside of compile time.

This is not to say that you cannot work with the data from mnesia tables without records, but you will not get the record syntax to work with them. For example you could read the result of an mnesia read into a map or list of {name, value} tuples and access it this way as described earlier in this thread.

1 Like

Reading the various replies here I realize I might be missing something. Can you provide a concrete example of what you are trying to do ? Use SQL or JSON to describe what you want to do.

I will try to simplify my approach as much as I can.

I want to write an erlang script which shows table records with fields. To be able to display mnesia table raws with field names, I should define “erlang record” with field/attribute list of table as follows;

-record(table_name, { field1, field2, field3, field4 }).

But my goal is to display the mnesia table raws with field names according to the table name provided bu user. In this context, I should be able to update erlang record at runtime due to the fact that table name is changed at runtime. My question is; is it possible to update “erlang record” definition at runtime? Another way comes to my mind, should I delete and re-create erlang record at runtime when table name changes? Or is my approach is not possible with erlang as cmkarlsson stated in the previous post, saying that erlang record is a compile time struct ?

If the record already exist in mnesia you don’t have to define it in your script, you can obtain the same information by using mnesia:table_info/2 as mentioned earlier.

This would address having your user provide the table name at runtime.

Is there another use case you are thinking about ?

Here is the script I wrote;

#!/usr/bin/env escript

-record(myRecordName, { field1, field2, field3, field4 }).
-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))).

main(_) ->
    Cookie = 'xxx',
    RemoteNode = 'xxx@xxx',
    net_kernel:start(['xxx@xxx',longnames]),
    erlang:set_cookie(node(),Cookie),
    InputTuple = io:fread("Enter table name >> ","~s"),
         io:format("Table name you entered : ~p~n", [InputTuple]),

    Input = element(2,InputTuple),
    io:format("User input  : ~s~n", [Input]),
    TableName = list_to_atom(lists:min( Input )),
    io:format("Table name  : ~s~n", [TableName]),
    %setelement(#myRecordName, list_to_tuple(mnesia:table_info(TableName, attributes)) ),
    get_field_names( RemoteNode, TableName ),
    get_records( RemoteNode, TableName ),
    get_records_with_fields( RemoteNode, TableName ),
    noop.

get_field_names(Rnode, TableName)->
   rpc:call( Rnode, mnesia, transaction, [
        fun()->io:format("Fields: ~p~n",[mnesia:table_info(TableName, attributes)])
        end
   ] ).

get_fields_as_tuple(Rnode, TableName)->
   rpc:call( Rnode, mnesia, transaction, [
        fun()->io:format("Fields in tuple format: ~p~n",[ list_to_tuple(mnesia:table_info(TableName, attributes)) ])
        end
   ] ).

get_records(Rnode, T) ->
    %% enumerate the records in a specific table
    rpc:call( Rnode, mnesia, transaction, [
        fun()->mnesia:foldl(
            fun(Rec,_Acc)->io:format("~p\n",[Rec]) end, [], T)
        end
   ] ).

get_records_with_fields(Rnode, T) ->
    %% enumerate the records in a specific table with corresponding fields 

    rpc:call( Rnode, mnesia, transaction, [
        fun()->mnesia:foldl(
            fun(Rec,_Acc)->io:format("~p\n",[?rec_info(myRecordName, Rec)]) end, [], T)

    end
  ] ).

As you stated, I can easily get table field list at runtime, but I want to bind this field list to “Erlang Record ” somehow at runtime . Otherwise, I can not display the records with fields names … I just want to change/update { field1, field2, field3, field4 } part of record definition with the new table field list…

I don’t quite understand why you can’t display the records with field names: the list of field names you get from mnesia:table_info/2 has a one-for-one correspondence to the record you get from mnesia:read/2. Originally you found something by Mats Cronqvist that showed this behaviour.

You can not change the -record(myRecordName, ....) because records don’t technically exist as a data-structure. Nor do you need to change it from escript.

The only time that is needed is if you are using the Erlang/Elixir shell and the shell has its own mechanisms for doing so.

I put my record definition into io:format while displaying table rows with fields in get_records_with_fields function as follows;

io:format("~p\n",[?rec_info(myRecordName, Rec)])

If you do not declare record with attributes list, table data is listed only as tuple without field names. But I want to display table with corresponding fields. Is it possible read table data with field names by using mnesia:read ?

No you can’t with mnesia:read

If you change the ?rec_info to use mnesia:table_info it would do what you are looking for.

@tty,
Could you please elaborate what you said while stating use mnesia:table_info in rec_info ? I am new in Erlang, so couldn’t understand what you mean exactly…