おらおらOratcl

●OracleとOratcl
Oracleといえば、RDBMS(リレーショナルデータベース管理システム)のトップブランドの一つです。 エンタープライズユースで基幹情報システム構築に広く使われる反面、 その有り余る機能とやけくそに高い値段からお茶の間や学校ではなじみのないソフトウェアではあります。 OratclはこのOracleのデータベースサーバに接続(ログオン)し、 OCIと呼ばれる専用のAPIを使ってOracleデータベースとデータをやりとりする Tclコマンドを使えるようにする拡張ライブラリです。 TclODBCのページで紹介した通り、 Oracle用のODBCドライバも入れていればODBC経由で接続することもできるのですが、 このOratclを使うと、 Oracleのストアドプロシージャ記述言語PL/SQLの記述ブロック(DECLARE〜BEGIN〜END) をTclスクリプトから直接投げつけることができ、 とても多機能なことが実現できます。 OratclはTom Poindexterさんによって開発され、 現在はオープンソースで開発、配布が行われています。
当サイトでは、Oratcl、Tcl/Tk、Oracleの次のバージョンによる環境の組み合わせで、 日本語の扱いを含めて動作が確認できたので、 これらの環境での動作を元に紹介します。

クライアントOSOratclTcl/TkOracle(Server)Oracle(Client)
MS-Windows 20004.08.4.1.0Oracle8 Enterprise Edition(8.0.5)Oracle Client 8.1.7
MS-Windows 20004.08.4.1.0Oracle8i Enterprise Edition(8.1.7)Oracle Client 8.1.7
MS-Windows XP4.48.5.2Oracle9i 9.0.2.5.0Oracle Client 8.1.7
Solaris 94.48.4.9Oracle9i 9.0.2.5.0Oracle Client 8.1.7
MS-Windows XP4.58.5.11Oracle9i 9.0.2.5.0Oracle Client 8.1.7

●バージョンにきをつけて
Oratclの最新版は4.5ですが、バージョン4台と3台の境をはじめ、 小数点1位の各バージョン間で一部(というかかな〜り)後方互換性が失われています。

●ダウンロード
Oratclの現在のサポートページはSourceForgeの このページです。 最新版のアーカイブの中には、Windows用コンパイル済DLLが同梱されています。 これはUNIX版のOracle製品しかもっていなくて、 Windows上でソースをビルドできない方には大助かりですし、何よりお手軽でよかよかです。

●Windows用バイナリDLLのインストール
Oratclはソース配布(tar.gz)の他、Windows用のバイナリDLLが配布されています。 これならOracleのクライアント(SQL*Net・NET8・Oracle8i Client〜)が入っていて、 SQL*Plusなどが使えるPCならアーカイブを展開し、DLLを取り出すだけで使えます。

配布をダウンロードして展開したら、 oratcl-4.4-winというディレクトリができるので、 その下の 「install.tcl」をエクスプローラでダブルクリックするなどして起動すれば一発です。
または私が試したところ、このディレクトリをTclホームの下のlibの中にコピーするだけでも使えました。

●ソース配布からのインストール・Windows編
ソース配布はTcl/Tkがインストール済みで、Cコンパイラが使え、 OracleのOCI(Oracle Call Interface) ライブラリがインストールされていればコンパイルできます。 最初の2つはいいとして、OCIはOracleの製品セットに入っているので、 これをインストールしているマシンで作業しなくてはいけません。 ここではTcl/Tk 8.3.0とOracle8i Personal Edition 8.1.6 が入っているMS-Windows98上でMicrosoft Visual C++ 4.0 (VC++) を使ってOratcl 2.7と3.0.1をコンパイルしてみたのでこの環境で紹介します。
Windows98用のおら8iには、VC++とボーランドのBorland C/C++ Compiler 用のOCIライブラリがついているので、このどちらかを使う必要があります (ここではVC++を選びました)。

  1. Cygwin Tarや市販フリーその他のツールを使い、ソースアーカイブを展開します。
      dos>tar xozf oratcl33.tar.gz
    
  2. winディレクトリに行き、「makefile.vc」を編集します。 サンプル(Oratcl 2.7)。 Oratcl 3.0.1の方のmakefile.vcはすぐ修正できるので、省略します。
  3. VC++のユーティリティNMAKEでコンパイルします。
    dos>SET INCLUDE=C:\MSDEV\INCLUDE
    dos>SET LIB=C:\MSDEV\LIB
    dos>SET PATH=C:\MSDEV\bin;%PATH%
    dos>NMAKE -f makefile.vc
    
  4. かなり警告が出るはずですが、とりあえずDLLができたらOKということで、 最初から同梱されているinstall.tclをエクスプローラでダブルクリックするなどして起動し、 Tcl/Tkインストールディレクトリの中にコピーさせて作業は完了です。

●ソース配布からのインストール・UNIX編
UNIXでは、新しいOratclにはconfigureスクリプトがついているので、 コンパイル自体はさほど難しくはありません。 が、今回試したOracle8は後述の落とし穴があるので要注意です。 ここでは、Oracle8 Enterprise Edition R8.0.6とTcl/Tk 8.3.3 が入っているSolaris 2.6にOratcl 3.2をインストールした際の作業手順をご紹介します。

  1. ソースアーカイブを展開し、そのディレクトリに入ります。
    % cd oratcl-3-2-branch
    
  2. Oracleの起動に必要な環境変数、特にORACLE_HOMEを正しくセットしておきます。
  3. configureスクリプトを起動します。 configureオプションの多くはTcl/Tk本体をインストールするときのものと同じですが、 --with-oracle-versionで明示的にOracleのバージョンを6,7,8の中から指定できます。
    % sh configure --enable-gcc --prefix=/opt/ora/local \
     --with-oracle-version=8
    
  4. あとは一本道です。make; make installでOKです。
    % make
    % su root
    # make install
    
では喜んで使ってまいりましょうか! オラって〜♪クルって〜♪(語感悪すぎ)
% package require Oratcl
couldn't load file "liboratcl3.2.so": ld.so.1:
 tclsh: fatal: relocation error: file
/opt2/local/app/oracle/product/8.0.6/lib/libclntsh.so.1.0:
symbol slpmprodstab: referenced symbol not found
あ、あら??
実はこれ、おら8の後半の版(8.0.4〜8.0.6)でよくPHPなどでも現れる現象で、 次のようにOracleのライブラリを強引に作り変えることで解決できます。
  1. $ORACLE_HOME/bin/genclntsh スクリプトを改造します。 真中やや後ろに下の行があるので、行頭に#をつけてコメント化します。
    # ar d $LIBCOMMON sorapt.o
    
  2. このgenclntshスクリプトを実行して、$ORACLE_HOME/lib/libclntsh.so を再編成します。
  3. シンボル「slpmprodstab」がライブラリに入ったことを確認するため、 シェルで次のコマンドを実行します。
    nm -A libclntsh.so | grep slpmprodstab 
    
    このよーな行が現れれば大丈夫です:
    libclntsh.so: [12972] |  4843548|   64|OBJT |GLOB |0  |15   |slpmprodstab 
    
これでひとまず解決です。対話型tclshで、「package require Oratcl」として、 「3.2」とバージョンの数字が出てくればOKです。
オラって〜♪クルって〜♪(そろそろ黙れ)

●2つ以上のOracleクライアントが入っている場合
どのOracleのOCIライブラリを使うかを、環境変数ORACLE_LIBRARYで設定できます。 UNIXならば(以下はcsh系のシェルの例)、

setenv ORACLE_LIBRARY /the/path/of/oracle/lib/libclntsh.so.1.0
のようにします。「lib」の代わりに「lib32」を使わないといけないかもしれません。

●ロード
Tclスクリプトに

package require Oratcl
と書くだけでOKです。(Oratclの1文字目は大文字です!!) または、正式なディレクトリに配置せずに
load /the/path/of/oratcl44.dll
こんな感じでもOKです。さっそく使ってみましょう。

●日本語データもOK
Tcl/Tk 8.1以降では、Tclは文字列を内部的にマルチバイト文字に対応したUnicodeの一種UTF-8で扱います。
一方Oracleがデータを内部に格納する文字セットして、UTF-8を選ぶことができますが、 それ以外にもシフトJIS(JA16SJIS)や日本語EUC(JA16EUC)も選べます。
そこで、内部文字セットがUTF-8ではないOracleデータベース・サーバーに接続して、 日本語を含むデータをやりとりするには、 UTF-8から日本語の何らかの文字コードに変換する処理が必要です。
マニュアルをよく読まない人(←鏡を指さす)が初めに思いつくのは、 Oracleクライアント用の環境変数NLS_LANGを使うことです。

set env(NLS_LANG) "Japanese_Japan.UTF8"
これは一見ナイスでうまくいくように見えますが、 Oratcl 4.0ころから問題が起きるようになりました。 Oracleから日本語データを取得して表示すると、末尾が何文字か欠けてしまうというものです。 おそらく、Oracle表の各列の幅ぶんの文字列バッファを確保して内部処理をする際、 日本語の全角文字はシフトJISの2バイトがUTF-8では3バイト分必要だという変換がうまくいっていないものと思います。 一応、「SELECT NAME FROM EMP_DB...」の代わりに 「SELECT RTRIM(RPAD(NAME,100)) FROM EMP_DB...」のように、 列幅を無理やり伸ばしてやるようなSQLにすれば、末尾が欠けるという現象はおさえられますが、 保守性も見た目もよくありません。もういや、こんな生活…
とうなだれる暇があればすべきことがあります。
それは、マニュアルを読むことだ!!(超偉そう)
解決策は「oraconfig」コマンドの「utfmode」をtrueにすることです。 これは、クライアントの利用環境やスクリプト全体に設定するものではなく、 後述の文ハンドルごとに設定するので、 コード・サンプルの際にご紹介したいと思います。

●接続と切断
OracleはRDBMSの例にもれずクライアント・サーバー型のソフトウェアで、 Oracleインスタンスとデータをやりとりするには最初に接続(ログオン)し、 処理が終わったら切断(ログオフ)する必要があります。 Oratclでは、接続はoralogon、 切断はoralogoffコマンドで行います。 「おら、ログオン」「おら、ログオフ」と覚えましょう(かなり恥ずかしいヤツ)。 接続に失敗したらエラーが飛びます。

set con [oralogon "DEVUSER/DEVPASS@DEVDB"]
#
# ... transaction
#
oralogoff $con
oralogonコマンドの引数はSQL*Plusの引数と全く同じで、 「ユーザ名/パスワード?@TNS接続文字列?」の形で指定します。 で、oralogonコマンドはTcl言語のopenやsocketなどと同様、 「ハンドル」を返すので、以後その接続で行うコマンドにはこのハンドルを使います。 上のように、最後にoralogoffで切断する時までハンドルは必要になります。


●makefile.vcのサンプル(Oratcl 2.7)

ORATCL_VERSION = 2.7
DLL_VERSION = 27
PROJECT = oratcl$(DLL_VERSION)
ROOT    = .
TMPDIR  = .
TOOLS32 = c:\msdev
TCL     = c:\progra~1\tcl
TCLLIB  = $(TCL)\lib\tclstub83.lib
ORACLE     = c:\oracle\ora81
ORACLE_OCI = $(ORACLE)\oci
ORACLE_LIB = $(ORACLE_OCI)\lib\msvc\oci.lib
NODEBUG=1
PATH=$(COMMON32)/bin;$(TOOLS32)\bin;$(PATH)

cc32    = $(TOOLS32)\bin\cl -I$(TOOLS32)\include
CP      = copy
RM      = del

INCLUDES = -I$(TOOLS32)\include -I$(TCL)\include 
DEFINES = -nologo $(DEBUGDEFINES) -DUSE_TCL_STUBS -DNO_BCOPY -DVERSION7 -DNONBLOCK_CURSOR \
-DVERSION=\"$(ORATCL_VERSION)\" -D__WIN32

!include 

.c.obj:
	$(cc32) $(cdebug) $(cflags) $(cvarsdll) $(INCLUDES) \
		$(DEFINES) -Fo$(TMPDIR)\ $<
DLLOBJS = oratcl.obj

all: $(PROJECT).dll

$(PROJECT).dll: $(DLLOBJS)
	$(link) $(linkdebug) $(dlllflags) $(ORACLE_LIB) $(TCLLIB) \
		$(guilibsdll) -out:$(PROJECT).dll $(DLLOBJS)
oratcl.obj: ..\generic\oratcl.c
	$(cc32) $(cdebug) $(cflags) $(cvarsdll) $(INCLUDES) \
		$(DEFINES) -Fo$(TMPDIR)\ $?
# end.

拡張レビュー分室 top
(first uploaded 2000/05/17 last updated 2012/05/27, MISUMI URANO)