Erlang ODBC 處理中文

erlang處理utf8字符集相對比較簡單,由於它是用integer的list來保存全部的string的,因此處理什麼字符集都不要緊。
話雖這麼說,但我在使用erlang的ODBC處理中文時,着實費了很多勁。

說實話,erlang的ODBC很差用,如今也有一些直接使用數據庫驅動的erlang庫,但都不怎麼成熟,項目裏不太敢用。 仍是用官方的ODBC踏實,並且換什麼數據庫都不用改代碼,方便。

開始時我覺得既然數據庫utf8的,我把erlang中二進制的utf8數據寫到數據庫表裏就能夠啦。後來發現,徹底不是那麼回事。erlang的ODBC並非原封不動將數據寫到表裏,它會根據字段的類型進行修改。

繞了好幾圈,終於找到一種方法能夠從表中讀寫中文啦。
下面就說一下個人方法。

測試的數據庫:MYSQL,Oracle

MySql字符集:

SHOW VARIABLES LIKE 'character_set%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8                       |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | utf8                       |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

Oracle字符集:

SQL> select * from v$nls_parameters;
+-----------------------------------------------------------------+-----------------------------------------------------------------+
| PARAMETER                                                       | VALUE                                                           |
+-----------------------------------------------------------------+-----------------------------------------------------------------+
| NLS_LANGUAGE                                                    | AMERICAN                                                        |
| NLS_TERRITORY                                                   | AMERICA                                                         |
| NLS_CURRENCY                                                    | $                                                               |
| NLS_ISO_CURRENCY                                                | AMERICA                                                         |
| NLS_NUMERIC_CHARACTERS                                          | .,                                                              |
| NLS_CALENDAR                                                    | GREGORIAN                                                       |
| NLS_DATE_FORMAT                                                 | DD-MON-RR                                                       |
| NLS_DATE_LANGUAGE                                               | AMERICAN                                                        |
| NLS_CHARACTERSET                                                | AL32UTF8                                                        |
| NLS_SORT                                                        | BINARY                                                          |
| NLS_TIME_FORMAT                                                 | HH.MI.SSXFF AM                                                  |
| NLS_TIMESTAMP_FORMAT                                            | DD-MON-RR HH.MI.SSXFF AM                                        |
| NLS_TIME_TZ_FORMAT                                              | HH.MI.SSXFF AM TZR                                              |
| NLS_TIMESTAMP_TZ_FORMAT                                         | DD-MON-RR HH.MI.SSXFF AM TZR                                    |
| NLS_DUAL_CURRENCY                                               | $                                                               |
| NLS_NCHAR_CHARACTERSET                                          | AL16UTF16                                                       |
| NLS_COMP                                                        | BINARY                                                          |
| NLS_LENGTH_SEMANTICS                                            | BYTE                                                            |
| NLS_NCHAR_CONV_EXCP                                             | FALSE                                                           |
+-----------------------------------------------------------------+-----------------------------------------------------------------+

測試的數據庫表:

mysql:

CREATE TABLE `t1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `f1` varchar(45) DEFAULT NULL
);

oracle:

create t1( "ID" NUMBER NOT NULL ENABLE,  "F1" VARCHAR2(45 CHAR));
注意: VARCHAR2字段「F1」的units是CHAR,並且必定要是CHAR(默認爲byte),爲何呢?後面會說


1、鏈接數據庫

odbc:connect("DSN=test", [{scrollable_cursors, off},  {binary_strings, on}])

注意,打開binary_strings,不打開應該也能夠,但下面的測試代碼須要作相應的修改。

2、代碼源文件格式

若是在代碼中有中文,須要將代碼源文件保存爲utf8格式,「而且」必須在代碼文件的第一行加:
%% -*- coding: utf-8 -*-

3、插入中文字段

3.一、使用odbc:sql_query(這個我沒測試過)

方法一:直接寫中文

odbc:sql_query(DB, "INSERT INTO t1(f1) VALUES('馬天才');")

ODBC返回出錯以下:
=ERROR REPORT==== 6-Aug-2013::16:29:09 ===
** Generic server <0.47.0> terminating 
** Last message in was {<0.46.0>,
                        {sql_query,[6,
                                    [73,78,83,69,82,84,32,73,78,84,79,32,116,
                                     49,40,102,49,41,32,86,65,76,85,69,83,40,
                                     39,39532,22825,25165,39,41,59]]},
                        infinity}
** When Server state == {state,#Port<0.758>,undefined,<0.46.0>,undefined,on,
                               false,false,off,connected,undefined,0,
                               [#Port<0.756>,#Port<0.757>],
                               #Port<0.759>,#Port<0.760>}
** Reason for termination == 
** {{badmatch,{error,einval}},
    [{odbc,odbc_send,2,[{file,"odbc.erl"},{line,832}]},
     {odbc,handle_msg,3,[{file,"odbc.erl"},{line,557}]},
     {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,588}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,239}]}]}

看到39532,22825,25165這三個數字了嗎?這是"馬天才「這三個字的codepoints,應該就是這個緣由。

方法二,轉爲utf8試試

    F = <<"馬天才"/utf8>>, %% <<233,169,172,229,164,169,230,137,141>>
    Sql = lists:flatten(io_lib:format("INSERT INTO t1(f1) VALUES('~s');",   [F])),
    odbc:sql_query(DB, Sql)

此次插入是成功啦,但插入到數據庫中是亂碼。

方法三,轉爲utf16(爲何要用utf16呢?有病亂投醫)

    F = <<"馬天才"/utf16-little>>, %% <<108,154,41,89,77,98>>
    Sql = lists:flatten(io_lib:format("INSERT INTO t1(f1) VALUES('~s');",   [F])),
    odbc:sql_query(DB, Sql)

此次插入也成功啦,但select返回時是<<108,0,154,0,41,0,89,0,77,0,98,0>>,爲何加一個0呀
數據庫中看仍然是亂碼。

3.2 用odbc:param_query(這個測試是OK的)

方法一:代碼裏直接中文

    D = <<"馬天才"/utf16-little>>, %% 中文必需要轉爲utf16-little
    odbc:param_query(DB, "INSERT INTO t1(f1) VALUES(?);", [{{sql_wvarchar, 45}, [D]}])

注意:1. 中文要用utf16-little,爲何? 看這裏 http://www.erlang.org/doc/apps/odbc/databases.html#id61509
2. 這裏要用sql_wvarchar,而不是sql_varchar,不然與sql_query的結果同樣。

方法二:程序收到的utf8

 U = <<233,169,172,229,164,169,230,137,141>>, %% 這是utf8的"馬天才「三個字
 D = unicode:characters_to_binary(U, utf8, {utf16, little}), %% 轉換爲utf16-little
 odbc:param_query(DB, "INSERT INTO t1(f1) VALUES(?);", [{{sql_wvarchar, 45}, [D]}])


4、查詢中文字段

 odbc:sql_query(DB, "select * from t1;")

4.1 在mysql裏返回正常

返回的f1字段的值是<<108,154,41,89,77,98>>,是utf16-little的編碼,
須要傳給其它程序顯示時,須要將其轉爲utf8,方法就是:
unicode:characters_to_binary(<<108,154,41,89,77,98>>, {utf16, little}, utf8)

4.2 在oracle裏有點問題

若是定義的varchar2字段「F1」的units是byte,則會返回<<???>>,這就是爲何上面說必定要定義爲char的緣由。
這是爲何呢?用erlang odbc看一下數據表的描述:  
odbc:describe_table(DB, "t1")
你會看到,mysql中是這樣:
[{"id",sql_integer},{"f1",{sql_wvarchar,45}}]

oracle中若是varchar2爲byte,則是這樣:
[{"ID",{sql_float,38}},{"F1",{sql_varchar,45}}]

若是改成char,則是這樣:
[{"ID",{sql_float,38}},{"F1",{sql_wvarchar,45}}]
也就是說,若是oracle中varchar2爲byte,則erlang會認爲它是sql_varchar類型,返回的結果不會轉換。
相關文章
相關標籤/搜索