接続方法が簡単になりました。「ElixirからOCIなしでOracleに接続する」
Elixirを勉強するにはDBアプリケーションを作成するのが個人的に一番捗るのですが、私のDB資産は全てOracle上にあります。
2015年12月27日現在EctoはOracleに対応していないので、何か接続方法はと探したところErlangからODBCドライバで接続可能。ということが判ったので試してみました。
環境は下記記事のDockerfileをdev環境にリビルドしたコンテナを使用しました。
・CentOS + Erlang + Elixir + PhoenixのDockerイメージで今日から始めるElixir on Phoenix入門
12/28追記: 記事中のDockerFileの設定ではCMDコマンドでPhoenixアプリが自動起動するので、最後に/bin/bashを加えてCMDを上書きするとよいでしょう。
12/29追記: コンテナ内からOTNのInstantClientファイルをダウンロードするのは困難なので、-v オプションでコンテナホスト間の共有ディレクトリを設定してホスト側から送りました。 例では双方iexshareというディレクトリを作成して共有しています。
sudo docker run -d -p 80:4000 -v /home/myuser/iexshare:/usr/local/src/phoenix/iexshare:rw --name phoenix -t myImage /bin/bash
Oracle ODBCドライバ for Linuxのインストール
OTNからInstant Clientに関する以下のファイルをダウンロードします。
instantclient-basic-linux.x64-12.1.0.2.0.zip
instantclient-sqlplus-linux.x64-12.1.0.2.0.zip
instantclient-odbc-linux.x64-12.1.0.1.0.zip
全てのファイルを以下に解凍します。
/opt/instantclient_12_1/
libaioのインストール
yum install -y libaio
起動スクリプトの設定
vi /etc/profile.d/oracle.sh
if [ -n $ORACLE_HOME ]
then
ORACLE_HOME=/opt/instantclient_12_1
export ORACLE_HOME
if [ -n LD_LIBRARY_PATH ] ; then
LD_LIBRARY_PATH=/opt/instantclient_12_1
else
LD_LIBRARY_PATH=/opt/instantclient_12_1:$LD_LIBRARY_PATH
fi
export LD_LIBRARY_PATH
export NLS_LANG=Japanese_Japan.AL32UTF8 #環境に合わせる
PATH=$ORACLE_HOME:$PATH
fi
スクリプトを有効化
$ source /etc/profile.d/oracle.sh
ODBC設定ファイルの場所を確認
odbcinst -jで設定ファイルの場所を確認します。
$odbcinst -j
unixODBC 2.3.1
DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /root/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8
ドライバの登録
vi /etc/odbcinst.ini
[Oracle12c]
Description = ODBC for Oracle12c
Driver = /opt/instantclient_12_1/libsqora.so.12.1
接続情報の登録
vi /etc/odbc.ini
[DSNORA12c]
Driver = Oracle12c
ServerName = 192.168.1.1:1521/ORCL #接続先の一例
isqlコマンドでODBC接続を確認
$ isql DSNOra12c myid passwd
+---------------------------------------+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| quit |
| |
+---------------------------------------+
SQL> select count(*) from user_tables;
+-----------------------------------------+
| COUNT(*) |
+-----------------------------------------+
| 55 |
+-----------------------------------------+
SQLRowCount returns -1
1 rows fetched
SQL> quit
ElixirからODBC接続
ODBCの開始
$ iex
iex(1)> :odbc.start
:ok
データソース(DSN)に接続
iex(2)> {:ok, conn} = :odbc.connect('DSN=DSNORA12c;UID=myid;PWD=passwd', \
...(2)> [{:scrollable_cursors, :off},{:binary_strings,:on}])
{:ok, #PID<0.65.0>}
接続IDとしてプロセスが返ってきてるのがわかります。
(データは自前のものです。紹介は省きます)
iex(3)> {:selected,cols,rows} = :odbc.sql_query(conn, 'SELECT TABLE_NAME,PCT_FREE,LAST_ANALYZED FROM User_tables WHERE ROWNUM<3')
{:selected, ['TABLE_NAME', 'PCT_FREE', 'LAST_ANALYZED'],
[{'TB_HOLIDAY', 10.0, {{2015, 5, 18}, {23, 9, 23}}},
{'TB_MYOFFICE', 10.0, {{2015, 5, 18}, {23, 9, 24}}}]}
結果セットはカラム名がリスト、データはタプルのリストのようです。日付時刻型もタプルですね。
2行目の一つ目のカラムを取り出してみます。
(もっとエレガントに取り出す方法があるはずですが、ド初心者につきご容赦ください)
iex(4)> elem( Enum.at(rows,1) , Enum.find_index(cols,fn col -> col=='TABLE_NAME' end))
'TB_MYOFFICE'
プロセスとしてのODBC接続
先ほど接続がプロセスで返ってきてるのが確認できたので
もう少し見てみます。
名称登録済みのプロセスを確認
iex(6)> Process.registered
[:init, :file_server_2, :user, :application_controller, :user_drv,
:standard_error, Logger.Supervisor, :global_group, :elixir_counter,
:elixir_config, :elixir_sup, IEx.Config, :elixir_code_server, Logger.Watcher,
:error_logger, :standard_error_sup, IEx.Supervisor, :kernel_safe_sup, :rex,
:erl_prim_loader, :odbc_sup, :inet_db, :kernel_sup, :code_server,
:global_name_server, Logger]
:odbc_supなるものがあります。
ODBC接続をもう一つ増やします
iex(2)> :odbc.connect('DSN=DSNORA12c;UID=myid;PWD=passwd', [{:scrollable_cursors, :off},{:binary_strings,:on}])
{:ok, #PID<0.67.0>}
プロセスがSupervisorであれば、count_children/1やwhich_children/1が使えるはずです。
Supervisorと仮定して子プロセスの様子を見てみます。
iex(8)> odbc_id=Process.whereis(:odbc_sup)
# PID<0.63.0>
iex(9)> Supervisor.count_children(odbc_id)
%{active: 2, specs: 1, supervisors: 0, workers: 2}
iex(10)> Supervisor.which_children(odbc_id)
[{:undefined, #PID<0.65.0>, :worker, [:odbc]},
{:undefined, #PID<0.67.0>, :worker, [:odbc]}]
:odbc_supプロセスに上記2プロセスがぶら下がってるのが確認できました。
別ノードですが、同様に2接続立ち上げたものをvisualixirで見てみます。
一目瞭然ですね。素晴らしい。
Elixir/ErlangからORACLEへはネイティブ接続がないのでがっかりしましたが、ODBCでも接続ごとにプロセスが作られるのを見ると自前でコネクションプールを作るのもハードルが低そうです。
ODBCのAPI仕様ですが、Erlangのリファレンスで調べることになります。この程度であれば、ErlangとElixirの文法の差は気にならないレベルですね。
12月31日追記 日本語対応のため:odbc.connectに[:binary_strings,on]を追加。