はじめに
ここではLinux上のユーザープログラムが、同じくLinux上で稼働するDBMSにODBC経由で接続するための手順を示す。前提とするプラットフォームおよびDBMSは以下のとおり。
- ユーザープログラムが稼働するOS : Alma Linux 9.5 DVD ISO
- ユーザープログラムを構成する技術 : gccで作成したネイティブアプリ
- DBMSが稼働するOS : Alma Linux 9.5 DVD ISO
- DBMS : PostgreSQL 17.2
- ODBC Driver : psqlODBC 17.00
- ODBC Driver Manager : unixODBC 2.3.9
ユーザープログラムとDBMSの関係
ユーザープログラムは、何らかのConnectorを介してDBMSに接続する。DBMSごとに多種のConnectorが提供されており、ODBCはそのひとつであり、代表的なものとなる。
ODBC経由でDBMSに接続するには、DBMSの開発元から提供されているODBC Driverを予めクライアント側(ユーザープログラム側の環境)に組み込んでおく必要がある。また、ユーザープログラムは、ODBC Driver Managerを介してODBC Driverに接続する。
Windowsの場合、ODBC Driver Managerは、OSに組み込まれているため、その存在を特別意識しなくても良かったが、Linuxの場合、ODBC Driverと同様に、ODBC Driver Managerもクライアント側に組み込んでおく必要がある。
Linux用のODBC Driver Managerとしては、下記のものがよく知られている。
unixODBC
- 概要: 最も一般的で広く使用されているオープンソースのODBC Driver Manager。
- 特徴: 多くのODBC Driverやアプリケーションでサポートされている。
- 利用シーン: 多くのLinuxディストリビューションにおける標準のODBC Driver Manager。
- ライセンス: GNU General Public License (GPL) および GNU Lesser General Public License (LGPL)
iODBC (Independent Open DataBase Connectivity)
- 概要: unixODBCと同様にオープンソースのODBC Driver Manager。最初にリリースされたODBC Driver Managerひとつで、クロスプラットフォーム対応を目指してる。
- 特徴: macOSや他のUnix系システムでも広く使用されている。
- 利用シーン: 特定の商用アプリケーションやiODBCに特化した環境で使われることがある。
- ライセンス: BSD License
環境構築
DBMS、ODBC Driver/Driver Managerのインストール
DBMSのインストールについては、数多くの情報源があり、その設定方法も様々であるため、ここでは詳細を事細かに示すことはせず、PostgreSQLに絞り、最低限の記載にとどめる。また、ODBC Driver Managerについては、unixODBCを使用する。
PostgreSQLの最低限の動作に必要な、下記のパッケージをダウンロードする。
postgresql17-libs-17.2-1PGDG.rhel9.x86_64.rpm
postgresql17-17.2-1PGDG.rhel9.x86_64.rpm
postgresql17-server-17.2-1PGDG.rhel9.x86_64.rpm
postgresql17-odbc-17.00.0004-1PGDG.rhel9.x86_64.rpm
上記は、PostgreSQLの公式サイトからダウンロード可能である。
https://download.postgresql.org/pub/repos/yum/17/redhat/rhel-9-x86_64/
また、unixODBCは、RPMのダウンロードサイトからダウンロード可能である。
unixODBC-devel-2.3.9-4.el9.x86_64.rpm
unixODBC-devel-2.3.9-4-devel.el9.x86_64.rpm
ダウンロード後、下記のコマンドでインストールする。インストールする順番を間違えるとエラーとなる。
$ sudo rpm -ivh postgresql17-libs-17.2-1PGDG.rhel9.x86_64.rpm
$ sudo rpm -ivh postgresql17-17.2-1PGDG.rhel9.x86_64.rpm
$ sudo rpm -ivh postgresql17-server-17.2-1PGDG.rhel9.x86_64.rpm
$ sudo rpm -ivh unixODBC-devel-2.3.9-4.el9.x86_64.rpm
$ sudo rpm -ivh unixODBC-devel-2.3.9-4-devel.el9.x86_64.rpm
$ sudo rpm -ivh postgresql17-odbc-17.00.0004-1PGDG.rhel9.x86_64.rpm
PostgreSQL、ODBC Driver Managerの設定
PostgreSQLおよびunixODBCのインストール後、PostgreSQLおよびunixODBCの初期設定を行う。postgresユーザーでinitdbを実行する。コマンド実行時にpostgresユーザーのパスワードの設定を求められる。ここではパスワード=postgresとする。
$sudu su - postgres
$ /usr/pgsql-17/bin/initdb -E UTF8 --locale=C -A scram-sha-256 -W
postgresユーザーのまま、/var/lib/pgsql/17/data/pg_hba.confが下記のようになっていることを確認する。
local all all scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
同じく/var/lib/pgsql/17/data/postgresql.confを下記のように編集する。
listen_address='localhost'
Port=5432
このあと、postgresユーザーをexitで抜けて下記を実行する。
$ sudo systemctl start postgresql-17.service
$ sudo systemctl enable postgresql-17.service
postgresユーザーとして、psqlでデータベース、テーブル、レコードを追加する。
$ psql -U postgres
# create database testdb;
# \l
... testdbを含むデータベース一覧が表示される ...
# \c testdb
# create table abc (id integer, name varchar(255));
# insert into abc values (0, 'hello');
上記で、\l はデータベース一覧を表示するコマンドで、\cはデータベースに接続するコマンドである。
postgresユーザーでのpsqlの実行後、psqlを抜けて、/etc/odbc.iniおよび/etc/odbcinst.iniを変更する。
# odbcinst.ini:
[PostgreSQL]
Driver=/usr/pgsql-17/lib/psqlodbcw.so
Setup=/usr/lib64/unixODBC/libodbcpsqlS.so
Driver64=/usr/pgsql-17/lib/psqlodbcw.so
Setup64=/usr/lib64/unixODBC/libodbcpsqlS.so
FileUsage=1
# odbc.ini:
[PostgreSQL_DS]
Driver=PostgreSQL
Database=testdb
Servername=localhost
Port=5432
UserName=postgres
Password=postgres
下記isqlでtestdbに接続し、abcテーブルの情報を参照できることを確認する。
$ isql PostgreSQL_DS
SQL> select * from abc;
ユーザープログラム
下記のプログラムは、DBMSに接続し、切断するだけのサンプルコードである。下記でコンパイルおよび実行することができる。
$ gcc testdb.cpp -lodbc -lodbcinst -o testdb
$ ./testdb
#include <cstdlib>
#include <cstdio>
#include <sql.h>
#include <sqlext.h>
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
// Return 0: Success, -1:Error
int open_database(SQLCHAR* connect_str, SQLCHAR statemsg[10], SQLCHAR msg[1024])
{
SQLINTEGER native; // This will not be refered from anywhere
SQLSMALLINT actual_msg_len; // This will not be refered from anywhere
// Alloc environment handle
if (SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv) == SQL_ERROR) {
if (henv != SQL_NULL_HENV) {
SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, statemsg, &native, msg, 1024, &actual_msg_len);
}
return -1;
}
SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3), 0);
// Alloc DB connection handle
if (SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) == SQL_ERROR) {
SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
// SQLDriverConnect
SQLCHAR conn_out[255]; // This will not be refered from anywhere
SQLSMALLINT conn_out_len; // This will not be refered from anywhere
SQLRETURN Ret = SQLDriverConnect(hdbc, NULL, connect_str, SQL_NTS, conn_out, 255, &conn_out_len, SQL_DRIVER_COMPLETE);
if (Ret == SQL_ERROR || Ret == SQL_SUCCESS_WITH_INFO) {
SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
// Alloc statement handle
if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) == SQL_ERROR) {
SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
return 0;
}
// Return 0: Success, -1:Error
int close_database(SQLCHAR statemsg[10], SQLCHAR msg[1024])
{
SQLINTEGER native; // This will not be refered from anywhere
SQLSMALLINT actual_msg_len; // This will not be refered from anywhere
// Free statement handle
if (SQLFreeHandle(SQL_HANDLE_STMT, hstmt) == SQL_ERROR) {
SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
// SQLDisconnect
SQLRETURN Ret = SQLDisconnect(hdbc);
if (Ret == SQL_ERROR || Ret == SQL_SUCCESS_WITH_INFO) {
SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
// Free DB connection handle
if (SQLFreeHandle(SQL_HANDLE_DBC, hdbc) == SQL_ERROR) {
SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
// Free environment handle
if (SQLFreeHandle(SQL_HANDLE_ENV, henv) == SQL_ERROR) {
SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, statemsg, &native, msg, 1024, &actual_msg_len);
return -1;
}
return 0;
}
int main(int argc, char* argv[])
{
SQLCHAR statemsg[10];
SQLCHAR msg[1024];
SQLCHAR connect_str[64] = "DSN=PostgreSQL_DS;";
if (open_database(connect_str, statemsg, msg) == -1) {
printf("statemsg=%s, msg=%s\n", (char*)statemsg, (char*)msg);
return -1;
}
if (close_database(statemsg, msg) == -1) {
printf("statemsg=%s, msg=%s\n", (char*)statemsg, (char*)msg);
return -1;
}
printf("success!\n");
return 0;
}
接続文字列の指定では、前述isqlの実行と同様にodbc.iniで設定したデータソース名'PostgreSQL_DS'を指定しているが、odbc.iniおよびodbcinst.iniに記載した個別設定をconnet_strに指定することもできる。
SQLCHAR connect_str[128] = "Driver=/usr/pgsql-17/lib/psqlodbcw.so;Server=127.0.0.1;Database=testdb;UID=postgres;PWD=postgres;Port=5432;";