幸いなことに、C言語とは縁のない人生でした。
が、突然、お客様から、C言語でDb2の更新処理バッチプログラムを書くと宣言されました。
今さらC言語?!と思いましたが、どうも静的SQLを使いたい意図がありそう。
静的SQLであれば、一度パッケージを作成した後はアクセスプランを固定した状態でSQLを実行できることと、都度SQLのコンパイルしなくて済むオーバーヘッドの軽さという利点が得られます。
(動的SQLでも PreparedStatementを使えば近い利点は得られますが、DBMSを再起動するとステートメントキャッシュは失われる)
C言語の開発スキルはありませんが、Db2用に書かれた組み込みSQLアプリを動かすまでに必要な一連のお手続きの流れと、Db2固有の前処理となるPRECOMPILEやBINDのオプションくらいは理解しておきたい。
Db2付属のCのサンプルプログラムを、ソースからコンパイルして動かしてみることができれば十分でしょう。
そして、今後また同じことで困惑しなくていいように、メモを残そうと思います。
使用した環境はDocker版Db2 11.5.0.0a。
※Db2サンプルプログラムにはmakefileが付録でついているので、本来はmakeコマンドを叩くだけで実行ファイルを作成することができます。今回はプリコンパイルやらバインドやらコンパイルやら、本来の組み込みSQLのCアプリケーション開発に必要なお手続きを体験したいために、わざわざmakeを使わない方法を試しています。ご了承ください。
Cの組み込みSQLアプリを動かすまでの流れ
おおまかには下記の手順で実行できました。
No. | 操作 | 何ができるか |
---|---|---|
1. | プリコンパイル | ソース・ファイル(xxx.sqc)からC言語ソース・ファイル(xxx.c)とバインドファイル(xxx.bnd)が生成される |
2. | バインド | バインドファイル(xxx.bnd)からパッケージが生成される |
3. | コンパイル | C言語ソース・ファイル(xxx.c)からオブジェクトファイル(xxx.o)が生成される |
4. | 実行 | 実行ファイル(xxx)が生成される |
コンパイル時は、オブジェクトファイル作成と、リンクとでコマンドを分けて実行したりしますが、ひとくくりにしています。
1. プリコンパイル
プリコンパイルは、組み込みSQLを含むソースファイルを処理して、バインドファイル(SQLが入る)と、SQL呼び出しを含むソースファイルを作成する処理。
(ここでパッケージが作成されるかはまだわからないので実際に試してからカタログを見てみる)
サンプルプログラムが置かれているディレクトリ(sqllib/samples/c) には権限がなくて書き込みできないので、作業用のディレクトリを作るところから。
[db2inst1@7471dc6b3181 ~]$ mkdir $HOME/work/c-comp
サンプルプログラム dbconn.sqc を作業用ディレクトリにコピーしてきます。
ものによっては、プログラム内で想定されるテーブルを作成しておかないとエラーになったりするので、エラーにならないやつを選んでいます。
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr
total 8
-r--r--r--. 1 db2inst1 db2iadm1 6155 Jun 11 05:31 dbconn.sqc
(※補足:Db2サンプルアプリ実行にあたっては、あわせて提供されている utilembやutilapi なども後々必要となりますが、コンパイルまではこれらのファイル無しでも進められます)
DBに接続する
[db2inst1@7471dc6b3181 c-comp]$ db2 connect to testdb
Database Connection Information
Database server = DB2/LINUXX8664 11.5.0.0
SQL authorization ID = DB2INST1
Local database alias = TESTDB
[db2inst1@7471dc6b3181 c-comp]$
プリコンパイルをする
[db2inst1@7471dc6b3181 c-comp]$ db2 "precompile ./dbconn.sqc BINDFILE using dbconn.bnd"
LINE MESSAGES FOR dbconn.sqc
------ -----------------------------------------------
SQL0060W The "C" precompiler is in progress.
SQL0091W Precompilation or binding was ended with "0"
errors and "0" warnings.
[db2inst1@7471dc6b3181 c-comp]$
sqc ファイルをプリコンパイルできたからには、「xxx.c」と「xxx.bnd」が出来ているはず。
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr
total 24
-r--r--r--. 1 db2inst1 db2iadm1 6155 Jun 11 05:31 dbconn.sqc
-rw-r--r--. 1 db2inst1 db2iadm1 541 Nov 27 02:21 dbconn.bnd
-rw-r--r--. 1 db2inst1 db2iadm1 10219 Nov 27 02:21 dbconn.c
[db2inst1@7471dc6b3181 c-comp]$
「db2conn.bnd」と「dbconn.c」が出来ました。
あと、パッケージは出来ているんだろうか。
[db2inst1@7471dc6b3181 c-comp]$ db2 "select count(*) from syscat.packages where boundby='DB2INST1' and pkgname='DBCONN'"
1
-----------
0
1 record(s) selected.
[db2inst1@7471dc6b3181 c-comp]$
まだパッケージは出来ていない。
つづいて、バインド。
2. バインド
バインドとは、バインドファイルからパッケージを作成し、データベースに保管する処理です。
バインドファイルって、テキストファイルなのかしら?
catで開いてみましたが、微妙。人手で編集する想定のファイルじゃなさそう。
読める範囲の部分を読むかぎり、DBに接続しているみたいですね。
”dbconn.sqc”はDB接続を行うアプリなのだから、当然か。
[db2inst1@7471dc6b3181 c-comp]$ cat dbconn.bnd
BINDV810LN▒▒
$8L\l|▒▒$!B$DB2INST1DBCONN hA1tIWLj▒!"$!$BEGIN DECLARE SECTIONGEND DECLARE SECTION|.CONNECT TO :H00001 USER :H0000CONNECT RESET▒H00001dbAlias▒▒H00002user▒H00003pswd
どうも、バインドファイルの内容を表示してくれる db2bfd というコマンドがあるらしい。
試してみると、これなら読みやすい。
どうやら、引数が3つ必要になりそうですね。
:H00001 → DB名 / :H00002 → 接続ユーザ名 / :H00003 → パスワード
[db2inst1@7471dc6b3181 c-comp]$ db2bfd -s ./dbconn.bnd
./dbconn.bnd: SQL Statements = 5
Line Sec Typ Var Len SQL statement text
---- --- --- --- --- ---------------------------------------------------------
67 0 5 0 21 BEGIN DECLARE SECTION
71 0 2 0 19 END DECLARE SECTION
124 0 19 3 46 CONNECT TO :H00001 USER :H00002 USING :H00003
130 0 19 1 19 CONNECT TO :H00001
175 0 19 0 13 CONNECT RESET
[db2inst1@7471dc6b3181 c-comp]$
バインドしてみる
[db2inst1@7471dc6b3181 c-comp]$ db2 "bind dbconn.bnd COLLECTION dbcon1"
LINE MESSAGES FOR dbconn.bnd
------ --------------------------------------------------------------------
SQL0061W The binder is in progress.
SQL0091N Binding was ended with "0" errors and "0" warnings.
[db2inst1@7471dc6b3181 c-comp]$
できたみたい。
パッケージは?
[db2inst1@7471dc6b3181 c-comp]$ db2 "select substr(pkgschema,1,16) as pkgschema,substr(pkgname,1,16) as pkgname,substr(boundby,1,16) as boundby,substr(owner,1,16) as owner,create_time, explicit_bind_time, alter_time from syscat.packages where boundby='DB2INST1' and pkgname='DBCONN'"
PKGSCHEMA PKGNAME BOUNDBY OWNER CREATE_TIME EXPLICIT_BIND_TIME ALTER_TIME
---------------- ---------------- ---------------- ---------------- -------------------------- -------------------------- --------------------------
DBCON1 DBCONN DB2INST1 DB2INST1 2019-11-27-02.23.02.323003 2019-11-27-02.23.02.323003 2019-11-27-02.23.02.323003
1 record(s) selected.
[db2inst1@7471dc6b3181 c-comp]$
できてる!
(※補足:サンプルアプリ実行にあたりBINDコマンドのCOLLECTIONオプション指定は不要で、「db2 bind $1.bnd」だけでOK。今回は、COLLECTIONオプションがパッケージのスキーマになることを知りたかった)
3. コンパイル
コンパイルを行うには、コンパイラが必要。
最低必要なレベルのコンパイラのバージョンは Db2マニュアルに記載されていて、下記のどちらかが入ってればいいらしい。
- GNU/Linux gcc バージョン 4.8 以上
- Intel C/C++ Compiler バージョン 16.0 以上
この環境には多分「gcc.x86_64 4.8.5-28.el7」が入っているから、条件は満たせていそうです。
[db2inst1@7471dc6b3181 c-comp]$ yum list | grep -i gcc
ovl: Error while doing RPMdb copy-up:
[Errno 13] Permission denied: '/var/lib/rpm/Packages'
Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast
gcc.x86_64 4.8.5-36.el7_6.2 @updates
(以下、略)
パスが通っているかも、念のため確認。→通っていました。
[db2inst1@7471dc6b3181 c-comp]$ which gcc
/bin/gcc
[db2inst1@7471dc6b3181 c-comp]$
[db2inst1@7471dc6b3181 c-comp]$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
[db2inst1@7471dc6b3181 c-comp]$
コンパイルにあたって、サンプルアプリコンパイルの前提となるユーティリティのコンパイルが事前に必要だったり、サンプルアプリに固有なオプションがあるので、makefile を読んで確認。
(参考:サンプルアプリの ReadMe )
makeを実行すると中で、「bldapp」というシェルスクリプトが呼び出される模様。この中でコンパイラ(GCC)が実行されています。
$CC $EXTRA_C_FLAGS -I$DB2PATH/include -c utilemb.c
$CC $EXTRA_C_FLAGS -I$DB2PATH/include -c utilapi.c
$CC $EXTRA_C_FLAGS -I$DB2PATH/include -c $1.c
$CC $EXTRA_C_FLAGS -o $1 $1.o utilemb.o $EXTRA_LFLAG > -L$DB2PATH/$LIB -ldb2
これを見ると、追加で「utilapl.c」「utilemb.c」が必要とわかります。
「utilapl.c」ファイルは元々あるので、サンプルアプリのディレクトリからコピーしてくれば、そのまま使えます。
「utilemb.c」は無く「utilemb.sqc」の状態で提供されているので、ワークディレクトリにコピーし、プリコンパイルしておきます。
# utilapi.c / utilapi.h ファイルを作業用ディレクトリにコピー
[db2inst1@7471dc6b3181 c-comp]$ cp -p $HOME/sqllib/samples/c/utilapi* ./
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr utilap*
-r--r--r--. 1 db2inst1 db2iadm1 4467 Jun 11 05:31 utilapi.h
-r--r--r--. 1 db2inst1 db2iadm1 9825 Jun 11 05:31 utilapi.c
# utilemb.c / utilemb.h ファイルを作業用ディレクトリにコピーしてprep
[db2inst1@7471dc6b3181 c-comp]$ cp -p $HOME/sqllib/samples/c/utilemb* ./
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr utilemb*
-r--r--r--. 1 db2inst1 db2iadm1 3301 Jun 11 05:31 utilemb.h
-r--r--r--. 1 db2inst1 db2iadm1 4043 Jun 11 05:31 utilemb.sqc
[db2inst1@7471dc6b3181 c-comp]$ db2 prep utilemb.sqc
LINE MESSAGES FOR utilemb.sqc
------ --------------------------------------------------------------------
SQL0060W The "C" precompiler is in progress.
SQL0091W Precompilation or binding was ended with "0"
errors and "0" warnings.
[db2inst1@7471dc6b3181 c-comp]$
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr utilemb*
-r--r--r--. 1 db2inst1 db2iadm1 3301 Jun 11 05:31 utilemb.h
-r--r--r--. 1 db2inst1 db2iadm1 4043 Jun 11 05:31 utilemb.sqc
-rw-r--r--. 1 db2inst1 db2iadm1 8561 Nov 27 02:31 utilemb.c
[db2inst1@7471dc6b3181 c-comp]$
UTILEMBパッケージも作成されています。
[db2inst1@7471dc6b3181 c-comp]$ db2 "select substr(pkgschema,1,16) as pkgschema,substr(pkgname,1,16) as pkgname,substr(boundby,1,16) as boundby,substr(owner,1,16) as owner,create_time, explicit_bind_time, alter_time from syscat.packages where boundby='DB2INST1' and pkgname='UTILEMB'"
PKGSCHEMA PKGNAME BOUNDBY OWNER CREATE_TIME EXPLICIT_BIND_TIME ALTER_TIME
---------------- ---------------- ---------------- ---------------- -------------------------- -------------------------- --------------------------
DB2INST1 UTILEMB DB2INST1 DB2INST1 2019-11-27-02.31.18.923009 2019-11-27-02.31.18.923009 2019-11-27-02.31.18.923009
1 record(s) selected.
[db2inst1@7471dc6b3181 c-comp]$
あとは bldapp の手順・オプションの通りにコンパイルしてみます。
まず、utilemb.c をコンパイルします。(オブジェクトファイルの作成)
[db2inst1@7471dc6b3181 c-comp]$ gcc -I/database/config/db2inst1/sqllib/include -c utilemb.c
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr utilemb*
-r--r--r--. 1 db2inst1 db2iadm1 3301 Jun 11 05:31 utilemb.h
-r--r--r--. 1 db2inst1 db2iadm1 4043 Jun 11 05:31 utilemb.sqc
-rw-r--r--. 1 db2inst1 db2iadm1 8561 Nov 27 02:31 utilemb.c
-rw-r--r--. 1 db2inst1 db2iadm1 12424 Nov 27 02:38 utilemb.o
[db2inst1@7471dc6b3181 c-comp]$
「utilemb.o」ファイルが生成されました。
続いて、utilapi.c をコンパイルします。(オブジェクトファイルの作成)
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr utilap*
-r--r--r--. 1 db2inst1 db2iadm1 4467 Jun 11 05:31 utilapi.h
-r--r--r--. 1 db2inst1 db2iadm1 9825 Jun 11 05:31 utilapi.c
-rw-r--r--. 1 db2inst1 db2iadm1 8184 Nov 27 02:39 utilapi.o
[db2inst1@7471dc6b3181 c-comp]$
「utilapi.o」ファイルが生成されました。
続いてdbconn.c をコンパイルします(オブジェクトファイルの作成)
[db2inst1@7471dc6b3181 c-comp]$ gcc -I/database/config/db2inst1/sqllib/include -c dbconn.c
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr dbconn*
-r--r--r--. 1 db2inst1 db2iadm1 6155 Jun 11 05:31 dbconn.sqc
-rw-r--r--. 1 db2inst1 db2iadm1 541 Nov 27 02:21 dbconn.bnd
-rw-r--r--. 1 db2inst1 db2iadm1 10219 Nov 27 02:21 dbconn.c
-rw-r--r--. 1 db2inst1 db2iadm1 7376 Nov 27 02:42 dbconn.o
[db2inst1@7471dc6b3181 c-comp]$
オブジェクトファイル「dbconn.o」が出来ました。
次に、「dbconn」という名前の実行ファイルを作成します。
[db2inst1@7471dc6b3181 c-comp]$ gcc -o dbconn dbconn.o utilemb.o -Wl,-rpath,/database/config/db2inst1/sqllib/lib64 -L/database/config/db2inst1/sqllib/lib64 -ldb2
[db2inst1@7471dc6b3181 c-comp]$ ls -ltr dbconn*
-r--r--r--. 1 db2inst1 db2iadm1 6155 Jun 11 05:31 dbconn.sqc
-rw-r--r--. 1 db2inst1 db2iadm1 541 Nov 27 02:21 dbconn.bnd
-rw-r--r--. 1 db2inst1 db2iadm1 10219 Nov 27 02:21 dbconn.c
-rw-r--r--. 1 db2inst1 db2iadm1 7376 Nov 27 02:42 dbconn.o
-rwxr-xr-x. 1 db2inst1 db2iadm1 18976 Nov 27 02:42 dbconn
[db2inst1@7471dc6b3181 c-comp]$
「dbconn」という実行可能ファイルが生成されました。
4. 実行してみる
引数は、バインドファイルに指定されていた3つか?
(:H00001 → DB名 / :H00002 → 接続ユーザ名 / :H00003 → パスワード)
試しに引数3つで dbconn を呼び出すと、USAGEが出てきました。
[db2inst1@7471dc6b3181 c-comp]$ ./dbconn testdb db2inst1 db2inst1
USAGE: ./dbconn [dbAlias [nodeName [userid passwd]]]
最低限必須なのはDB名だけと思われるので、DB名だけ指定して実行
--> 動きました!
[db2inst1@7471dc6b3181 c-comp]$ ./dbconn testdb
THIS SAMPLE SHOWS HOW TO CONNECT TO/DISCONNECT FROM DATABASES.
-----------------------------------------------------------
USE THE SQL STATEMENT:
CONNECT TO
TO CONNECT TO A DATABASE.
Execute the statement
CONNECT TO testdb
-----------------------------------------------------------
USE THE DB2 API:
db2DatabaseRestart -- RESTART DATABASE
TO RESTART A DATABASE.
Restart a database.
database alias: testdb
-----------------------------------------------------------
USE THE SQL STATEMENT:
CONNECT RESET
TO DISCONNECT FROM THE CURRENT DATABASE.
Execute the statement
CONNECT RESET
[db2inst1@7471dc6b3181 c-comp]$
(以上)