ElixirからOracle接続

[ 接続方法が簡単になりました。「ElixirからOCIなしでOracleに接続する」](https://qiita.com/twinbee/items/3cdba715ba42631d43b3)


Elixirを勉強するにはDBアプリケーションを作成するのが個人的に一番捗るのですが、私のDB資産は全てOracle上にあります。
　2015年12月27日現在EctoはOracleに対応していないので、何か接続方法はと探したところErlangからODBCドライバで接続可能。ということが判ったので試してみました。


環境は下記記事のDockerfileをdev環境にリビルドしたコンテナを使用しました。
・[CentOS + Erlang + Elixir + PhoenixのDockerイメージで今日から始めるElixir on Phoenix入門](http://qiita.com/xtity/items/969eefb83fcac9023542)

12/28追記: 記事中のDockerFileの設定ではCMDコマンドでPhoenixアプリが自動起動するので、最後に/bin/bashを加えてCMDを上書きするとよいでしょう。

12/29追記: コンテナ内からOTNのInstantClientファイルをダウンロードするのは困難なので、-v オプションでコンテナホスト間の共有ディレクトリを設定してホスト側から送りました。　例では双方iexshareというディレクトリを作成して共有しています。

```bashsh:
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のインストール

```bashsh:
yum install -y libaio
```
###起動スクリプトの設定
vi /etc/profile.d/oracle.sh

```csh: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
```
スクリプトを有効化

```bash:
$ source /etc/profile.d/oracle.sh
```
###ODBC設定ファイルの場所を確認
odbcinst -jで設定ファイルの場所を確認します。

```bash:
$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

```ini:odbcinst.ini
[Oracle12c]
Description     = ODBC for Oracle12c
Driver          = /opt/instantclient_12_1/libsqora.so.12.1
```
接続情報の登録
vi /etc/odbc.ini

```ini: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
iex(1)> :odbc.start
:ok
```
データソース(DSN)に接続

```iex: 
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:
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:
iex(4)> elem( Enum.at(rows,1) , Enum.find_index(cols,fn col -> col=='TABLE_NAME' end))
'TB_MYOFFICE'
```

#プロセスとしてのODBC接続
先ほど接続がプロセスで返ってきてるのが確認できたので
もう少し見てみます。
名称登録済みのプロセスを確認

```iex: 
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: 
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: 
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プロセスがぶら下がってるのが確認できました。

別ノードですが、同様に２接続立ち上げたものを[visualixir](http://qiita.com/massn-san/items/5b2f2646200747515214)で見てみます。

![visualixir.png](https://qiita-image-store.s3.amazonaws.com/0/66748/1b78d75b-63bf-dd93-d3ba-98325f66f615.png)

一目瞭然ですね。素晴らしい。

Elixir/ErlangからORACLEへはネイティブ接続がないのでがっかりしましたが、ODBCでも接続ごとにプロセスが作られるのを見ると自前でコネクションプールを作るのもハードルが低そうです。

ODBCのAPI仕様ですが、Erlangのリファレンスで調べることになります。この程度であれば、ErlangとElixirの文法の差は気にならないレベルですね。

参考　[Erlang ODBC リファレンス](http://www.erlang.org/doc/man/odbc.html 
"Erlang ODBC")

12月31日追記 日本語対応のため:odbc.connectに[:binary_strings,on]を追加。