刹那(せつな)の瞬き

Willkömmen! Ich heiße Setsuna. Haben Sie etwas Zeit für mich?

UbuntuでFreeTDSとtdsppからSQLServerに接続する

久しぶりに FreeTDS を扱う機会を得たので、FreeTDS そのものというか、ODBC を使わずに、C++SQL Server on Linux に接続してみたくなりました。

とは言っても、直接 DB-Library / CT-Library を扱うつもりはなく、FreeTDS の Wikipedia にリンクが貼ってある C++ wrapper な TDS++ (tdspp) 経由です。

GitHub に TDS7.4 対応済みのものを見つけたので、これを試してみます。

1. 環境

  • Ubuntu Desktop 18.04.4 LTS 日本語Remix (Linux Kernel 5.3.0-42-generic)
  • SQL Server 2019 (RTM-CU3) (KB4538853) - 15.0.4023.6 (X64) on Linux
  • g++ 7.5.0
  • NorthwindJ データベース ※Node.js と Qt5 から正常に接続できる事を確認済み。

以降の内容は、あくまで tdspp の評価が目的です。常用するつもりはありません。

2. 準備

(1) パッケージ追加 ※未導入の場合

まずは、FreeTDS のヘッダとライブラリを用意します。

$ sudo apt install freetds-dev

私の環境では、バージョン 1.00.82-2ubuntu0.1 が導入されました。

(2) 作業ディレクト
$ cd ~
$ mkdir work
$ cd work
(3) リポジトリのクローン

GitHub から tdspp のリポジトリを取ってきます。

$ git clone https://github.com/calanor/tdspp.git
Cloning into 'tdspp'...
remote: Enumerating objects: 78, done.
remote: Total 78 (delta 0), reused 0 (delta 0), pack-reused 78
Unpacking objects: 100% (78/78), done.
$ cd tdspp

これでコンパイル環境は~/work/tdspp/になります。

3. ソース修正

tdspp そのものは修正しない方針で進めます。

(1) Makefile の修正

些細な事ですが、Makefile に不備があるので、先に修正します。

installの最下行の一つ前にある、@ln 〜 の行をコメントアウトします。

install:: $(LIB) 
	@ls -l $(LIB)
	@echo "Installing $(SONAME) into $(PREFIX)"
	@mkdir -p $(PREFIX)/lib/
	@mkdir -p $(PREFIX)/include/$(LIBNAME)
	install -o root -g bin -m 644 $(STRIPFLAG) $(LIB) $(PREFIX)/lib/
#	@ln -fs $(PREFIX)/lib/$(LIB) $(PREFIX)/lib/$(SONAME)
	install -o root -g bin -m 644 *.hh $(PREFIX)/include/$(LIBNAME)

※LIB のコメントを入れ替えても良いですが、今回は評価だけなので、これで済ませます。

(2) main.cc の修正

データベースへの接続情報を実際の環境に合わせます。

        /* Connect to database. */
        //db->connect("localhost:1433", "admin", "12345678");
        db->connect("localhost:1433", "sa", "abcd1234$");

4. ライブラリ作成とインストール

(1) コンパイル

./configure は存在しないので、そのまま make します。

$ make
g++ -o tdspp.o -I/usr/local/include -c -Wall -ansi -fPIC -std=c++11 tdspp.cc
g++ -o query.o -I/usr/local/include -c -Wall -ansi -fPIC -std=c++11 query.cc
g++ -o field.o -I/usr/local/include -c -Wall -ansi -fPIC -std=c++11 field.cc
g++ -o libtds++.so -Wall -ansi -fPIC -std=c++11 -I/usr/local/include -L/usr/local/lib -lct -shared -ldl tdspp.o query.o field.o
$

make が成功すれば、カレントディレクトリにlibtds++.soが作られます。

(2) インストール

ヘッダとライブラリを OS 環境にインストールします。

$ sudo make install
  .......
Installing libtds++.so into /usr/local
install -o root -g bin -m 644 -s libtds++.so /usr/local/lib/
install -o root -g bin -m 644 *.hh /usr/local/include/tds++
$
(3) ライブラリの設定

/etc/ld.so.conf/usr/include/libを追加してsudo ldconfigしますが、後で削除する予定なので、今回は何もしません。

その代わりに、サンプルを実行する環境で、一時的に LD_LIBRARY_PATH を設定します。

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

5. サンプルの実行

(1) コンパイルと実行結果
$ make main
g++ -g -std=c++11 main.cc -o main -L/usr/local/lib -ltds++ -lct
$ ./main
| datetime_type | right_now | integer_type | float_type | 
Results:
| Mar 23 2020 08:19PM | Mar 23 2020 08:19PM | 1 | 0.1 |
(2) ロケール設定を追加してみる

main.cc にロケール設定を追加します。

int main(int argc, char **argv) {
    setlocale(LC_ALL, "");   // <--追加
    TDSPP *db = new TDSPP();
(3) リコンパイルと実行結果
$ make main
g++ -g -std=c++11 main.cc -o main -L/usr/local/lib -ltds++ -lct
$ ./main
| datetime_type | right_now | integer_type | float_type | 
Results:
|  3月 23 2020 08:25午後 |  3月 23 2020 08:25午後 | 1 | 0.1 |

日本語ロケールを考慮した、想定通りの結果です。

6. ちょっとだけ遊んでみた

日本語を含むクエリ文字列を実行して、int, nvarchar, datetime のデータ型を処理してみます。

(1) ソース修正

main.cc の中身を入れ替えます。

#include <iostream>
#include <locale>
#include <string>
#include <ctime>
#include <ctpublic.h>
#include "tdspp.hh"

int main(int argc, char **argv) {
    std::locale::global(std::locale(""));
    TDSPP *db = new TDSPP();
    try {
        db->connect("localhost:1433", "sa", "abcd1234$");
        db->execute("use NorthwindJ");

        std::string sql_text = "SELECT TOP 5 社員コード,氏名,CONVERT(nvarchar, 誕生日, 111) AS 誕生日 FROM 社員";
        Query *qry = db->sql(sql_text);
        try {
            qry->execute();
            std::cout << sql_text << std::endl;
            std::cout << "結果:" << std::endl;
            qry->rows->printheader();
            while (!qry->eof()) {
                std::cout << "| ";
                // fields(0)
                int id = qry->fields("社員コード")->to_int();
                std::cout << id << " | ";
                // fields(1)
                std::string name(qry->fields("氏名")->to_str());
                std::cout << name << " | ";
                // fields(2)
                std::string tmp(qry->fields("誕生日")->to_str());
                std::tm birthday;
                if (strptime(tmp.c_str(), "%Y/%m/%d", &birthday) == NULL) {
                    std::cout << tmp << " | ";
                } else {
                    char buff[40];
                    std::strftime(buff, sizeof(buff), "%Y-%m-%d", &birthday);
                    std::cout << buff << " | ";
                }
                std::cout << std::endl;
                qry->next();
            }
        }
        catch(TDSPP::Exception &e) {
            std::cerr << e.message << std::endl;
        }
        delete qry;
    }
    catch(TDSPP::Exception &e) {
        std::cerr << e.message << std::endl;
    }
    delete db;
    return 0;    
}

datetime 型は tdspp で考慮されていないデータ型なので、文字列で受けて強引に処理してます。

(2) コンパイルと実行結果
$ make main
g++ -g -std=c++11 main.cc -o main -L/usr/local/lib -ltds++ -lct
$ ./main
SELECT TOP 5 社員コード,氏名,CONVERT(nvarchar, 誕生日, 111) AS 誕生日 FROM 社員
結果:
| 105 | 森上 偉久馬 | 1967-10-25 | 
| 107 | 葛城 孝史 | 1961-02-03 | 
| 110 | 加藤 泰江 | 1968-02-02 | 
| 204 | 川村 匡 | 1957-12-08 | 
| 207 | 松沢 誠一 | 1965-03-30 | 

日本語を含むクエリ文字列でも問題なく処理できたので、概ね満足です。

7. 補足

(1) tdspp のヘッダとライブラリの削除

make clean では削除されません。
そして、make uninstall 等も実装されていないので、手動で削除します。

$ sudo rm -r /usr/local/include/tds++/
$ sudo rm /usr/local/lib/libtds++.so
(2) 所感

本格的に tdspp を利用するなら、tdspp にも手を加えないとです。
C と DB-Library だけの時代と比べたら天国だとしても、やっぱり手間ですね。

クエリを工夫すれば、ある程度までは unixODBC 無し / ODBC ドライバ無しでも何とかなりそうだけど...

基本的には ODBC なので、もうこのパターンでコーディングする機会はほぼないかな。