2012-09-07

C言語でのOracle外部プロシージャ

Oracleの外部プロシージャとは、簡単に言うと、PL/SQL以外の言語で書かれたストアドプロシージャのことを指す。 Oracle本家でサポートしているのは以下の3つ。 サードパーティーでは perl が使える。 Oracle® Databaseアドバンスト・アプリケーション開発者ガイドを参照しながらC言語で簡単な外部プロシージャを作ってみる。
まず、system ユーザーでログインして試験用ユーザーを作成
~$ sqlplus

SQL*Plus: Release 10.2.0.1.0 - Production on Fri Sep 7 13:22:00 2012

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Enter user-name: system/manager

Connected to:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production

SQL> create user extproc_test identified by extproc_test                    
  2  default tablespace users temporary tablespace temp;

User created.

SQL> grant connect, resource, create library to extproc_test;

Grant succeeded.

次に試験用ユーザーでログインして、共有ライブラリの場所を指定。
SQL> connect extproc_test/extproc_test 
Connected.
SQL> create library extproc_test as '${ORACLE_HOME}/lib/extproc_test.so';
  2  /

Library created.

$ORACLE_HOME/lib, $ORACLE_HOME/bin 以外の場所に共有ライブラリを置くときは 環境変数EXTPROC_DLLSを設定すること。
次にSQL関数を作成。
SQL> create function add_func(x pls_integer, y pls_integer)                       
  2  return pls_integer
  3  as language C library extproc_test name "add_func";
  4  /

Function created.

次にC言語の関数を作成。
int add_func(int x, int y)
{
  return x + y;
}
pls_integer はデフォルトでは C 言語の int にマッピングされる。
コンパイルして $ORACLE_HOME/lib にコピー。
$ gcc -shared -o extproc_test.so extproc_test.c
$ cp extproc_test.so $ORACLE_HOME/lib
実行してみる。
SQL> select add_func(1, 2) from dual;

ADD_FUNC(1,2)
-------------
            3
期待通りに動いた。
試しにNULLを渡してみる。
SQL> select add_func(1, NULL) from dual;
select add_func(1, NULL) from dual
                              *
ERROR at line 1:
ORA-01405: fetched column value is NULL
C言語では NULL かどうかの判断ができないから当然か。。。
C言語側にNULLかどうかのフラグを渡せるよう関数定義を修正する。
SQL> create or replace function add_func(x pls_integer, y pls_integer)
  2  return pls_integer
  3  as language c library extproc_test name "add_func"
  4  parameters(x, x indicator, y, y indicator, return indicator);
  5  /

Function created.

parameters の指定に従い、C言語の関数定義も変更する。
#include 

int add_func(int x, short x_ind, int y, short y_ind, short *rind)
{
  if (x_ind || y_ind) { /* x または y が NULL のとき */
    *rind = OCI_IND_NULL; /* 戻り値は NULL */
    return 0;
  }
  *rind = 0; /* 戻り値は非NULL */
  return x + y; /* 戻り値 */
}
再度コンパイルしてコピー。
$ gcc -shared -o extproc_test.so extproc_test.c -I$ORACLE_HOME/rdbms/public
$ cp extproc_test.so $ORACLE_HOME/lib
実行してみる。
SQL> select add_func(1, NULL) from dual;
select add_func(1, NULL) from dual
                              *
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
使用中の共有ライブラリを上書きしたせいか、それともメモリ上には古い共有ライブラリが 載っているのにOracle側の関数定義を変えたせいか。 まあ、どちらにしても、これでextprocプロセスが再起動してくれれば次はうまく行くはず。
SQL> select add_func(1, NULL) from dual;

ADD_FUNC(1,NULL)
----------------

今度は動いた。
念のため非NULL値の試験も行う。
SQL> select add_func(1, 2) from dual;

ADD_FUNC(1,2)
-------------
            3
動いた。

以上。