mnesia使用中文创建数据库并支持REST操作

这个不是什么高深的内容,记录下来,给自己提个醒。

起源于我想给我的所有数据库添加一个统一的操作接口,当然,使用英
文会更方便,更批量化,代码更少等等,我在弄的时候突发奇想,使用中
文吧,这样网页中看着连想都不用想,而且还可以给别人看,连教育部都
不支持中文了,杂家也随下大流。

其实erlang的record完全支持中文的声明,比如下面的例子:

      -record('公告',
        {'ID', %ID
         '标题' = "", % 标题
         '内容' = "", % 描述
         '显示' = true% 禁用
        }).
      

只所以可以成功是因为单引号的字符串,在erlang中就是作为一个atom
类型的,如果你想给里面加个空格什么的,也没问题。当然,使用的时候也
要加上单引号。

其实,erlang的record使用中文的话,也就上面的这些。但如果在shell
中使用,就比较烦人了,在命令行下使用下面的语句:

     mnesia:transaction(fun() -> mnesia:match_object('公告') end)).
     

会出现:illegal atom 的错误,或许修改环境变量的编码可以修改,我
没有测试。

接下来是http的REST接口,一般都使用的是表格显示,我喜欢使用JSON数
据格式,这个就更简单些了。还是上面的record:

     info(Db) ->
    Record = mnesia:table_info(Db, attributes),  
    [
     {[{"name", list_to_binary(atom_to_list(X))}, {"field", list_to_binary(atom_to_list(X))}],
      X
     } || X<- Record
    ].
    db_get_layout(Db) ->
    [X || {X, _} <- info(Db)].
     

上面的代码是中英文通用的,如果传入的Db是中文的话,这个必须首先创
建了数据库才可以调用,这里可能有一个问题:

为什么不用atom_to_binary函数?

我也想用啊,但使用atom_to_binary(X, utf8)在utf8编码下,客户端显
示出来是乱码,当然,一切的文件和环境都是utf8编码,因此,不存在什么
编码不统一的问题,我是使用dojo来显示的。总之,上面的解决了我的问题。

上面的代码仅生成一个list,如何生成json的结构呢,很简单:

     mochijson2:encode(db_get_layout('公告')).
     

在info函数中,生成的每一项是一个长度为2的tuple,第二个元素是在获
取的时候做匹配用的:

          put_record([], _Db, _, _Data, Result) ->
    lists:reverse(Result);
put_record([Record|Rest], Db, N, Data, Result) ->
    put_record(Rest, Db, N+1, Data, [data_to_json(Db, Record, element(N, Data)) | Result]).

          db_get(Db) ->
    case db:find(Db) of
        {atomic, []} ->
            [];
        {atomic, List} ->
            Info = [X || {_, X} <- info(Db)],
            F = fun(One) ->
                        put_record(Info, Db, 2, One, [])
                end,
            [F(X) || X<- List]
    end.
     

这里有一个data_to_json的函数,只所以将每一项都调用这个函数处理是
因为数据可能要自定义格式来显示,虽然可以使用通用的io_lib:format函数
来统一实现,但谁也不希望一个字符串使用列表的方式表现出来或者显示乱
码吧。因此,put_record就可以这样写了:

     data_to_json(_Db, 'ID', Data) ->
    {<<"ID">>, Data}; 
data_to_json('公告', '标题', Data) ->
    {<<"标题">>, list_to_binary(Data)};
data_to_json('公告', '显示', Data) ->
    {<<"显示">>, Data};
data_to_json('公告', '内容', Data) ->
    {<<"内容">>, list_to_binary(Data)};
data_to_json(_, P, Data) ->
    {list_to_binary(atom_to_list(P)), Data}.
     

我们假设所有的ID都是统一的,一般也都是统一的,所以可以忽略掉Db的
参数了,其他的带上Db的参数的话,在匹配时只针对这个Db的指定数据段。

将生成的数据转换为json不用提了,之前就有。接下来说数据的更新或添
加,我希望的类似上传格式是这样子的:

     [{
 "ID": 15,
 "标题": "中风的萨芬撒",
 "内容": "fdsafs中文范德萨减肥的扫过地横扫",
 "显示": true
},
{
 "ID": 16,
 "标题": "中风的萨芬撒",
 "内容": "fdsafs中文范德萨减肥的扫过地横扫",
 "显示": true
}
]
     

上面的文件内容要求是UTF-8编码,每个字段的字段名就是表头的名字,也
就是数据中中的每一项的名字。为了统一处理,我在接受到的时候,做了一下
总体的封装:

    loop(Req, DocRoot) ->
    case Req:get(method) ->
         'PUT' ->
               case Path of
                    "db/" ++ Db ->
                          Db = list_to_existing_atom(Db),
                          case [X || X<- mnesia:system_info(tables),
                                     X =:= Db] of
                           [] ->
                              Req:ok({"text/plain", "not found db"});
                           [Db] ->
                                PostData = Req:recv_body(),
                                db_put(Db, mochijson2:decode(PostData, [{format, proplist}])),
                              Req:ok({"text/plain", "db update"});
                           [_] ->
                               Req:ok({"text/plain", "db not support"})
                          end;
                   _ ->
                     Req:ok({"text/plain", "not support"})
              end;
        ...... 
    

首先会将JSON格式统一转换为proplist所支持的格式,然后传递给db_put
函数,db_put函数及其他内容如下:

         write('公告', Data) ->
    Ret = case proplists:get_value('ID', Data, 0) of
              0 ->
                  #'公告'{
                     'ID' = counter:bump('公告'),
                     '标题' = proplists:get_value('标题', Data, ""),
                     '内容' = proplists:get_value('内容', Data, ""),
                     '显示' = proplists:get_value('显示', Data, true)
                    };
              Id ->
                  case db:find('公告', Id) of
                      {atomic, []} ->
                          #'公告' {
                             'ID' = Id,
                             '标题' = proplists:get_value('标题', Data, ""),
                             '内容' = proplists:get_value('内容', Data, ""),
                             '显示' = proplists:get_value('显示', Data, true)
                            };
                      {atomic, [T]} ->
                          T#'公告'{
                            '标题' = proplists:get_value('标题', Data, T#'公告'.'标题'),
                            '内容' = proplists:get_value('内容', Data, T#'公告'.'内容'),
                            '显示' = proplists:get_value('显示', Data, T#'公告'.'显示')
                           }
                  end
          end,
    {ok, Ret};
write(_A, Data) ->
    {error, Data}.

db_put(Db, []) ->
    ok;
db_put(Db, [A|Rest]) ->
    case check(Db, A, {ok, []}) of
        {ok, Data} ->
            case write(Db, Data) of
                {ok, Write} ->
                    mnesia:transaction(fun() -> mnesia:write(Write) end);
                {error, Data} ->
                    io:format("Error~p~n", [Data])
            end;
        Error ->
            io:format("Error=~p~n", [Error])
    end,
    db_put(Db, Rest).

check(_Db, [], Result) ->
    Result;
check(Db, [A|Rest], {Err, Result}) ->
    case check(Db, A) of
        {ok, Data} ->
            check(Db, Rest, {ok, [Data|Result]});
        {error, D} ->
            {error, D}
    end.

check('公告', {<<"显示">>, Data}) -> 
    case Data of
        true ->
            {ok, {'显示', true}};
        false ->
            {ok, {'显示', false}};
        _ ->
            {error, Data}
    end;
check('公告', {<<"标题">>, Data}) -> 
    case is_binary(Data) and (byte_size(Data)> 0) of
        true ->
            {ok, {'标题', binary_to_list(Data)}};
        _ ->
            {error, Data}
    end;
check('公告', {<<"内容">>, Data}) -> 
    case is_binary(Data) and (byte_size(Data)> 0) of
        true ->
            {ok, {'内容', binary_to_list(Data)}};
        _ ->
            {error, Data}
    end;
check(_Db, {<<"ID">>, Id}) -> 
    case is_integer(Id) and (Id > 0) of
        true ->
            {ok, {'ID', Id}};
        _ ->
            {error, Id}
    end.

    

因为每个数据库的每个字段需要的检查内容可能都不一样, 但也可能一样,
使用上面的方式,可以灵活定制下。所有的内容项检查完毕后,交由write函数
进行封装,write函数会针对每个数据库的需要进行最后的检查,检查通过之后,
生成对应的数据格式, 如果一切都无误的话,写入到数据库。

REST数据可以使用curl方便进行操作,比如:

        curl -T test.txt -X PUT http://127.0.0.1:8080/db/公告
   

以上内容所有文件均在UTF-8编码下测试完成。

总体而言,还是英文好些,至少不用中英文一直切换那个烦啊。对于本地语
言的定制,或许做一个翻译对应的ets是比较好的方式了。

发布者

rix

如果连自己都不爱自己,哪还有谁来爱你