LoginSignup
11
9

More than 5 years have passed since last update.

ElixirからOracle接続

Last updated at Posted at 2015-12-27

接続方法が簡単になりました。「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

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

odbcinst.ini
[Oracle12c]
Description     = ODBC for Oracle12c
Driver          = /opt/instantclient_12_1/libsqora.so.12.1

接続情報の登録
vi /etc/odbc.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(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で見てみます。

visualixir.png

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

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

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

参考 Erlang ODBC リファレンス

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

11
9
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9