導入編では nanodbc をビルドして Ubuntu 環境にインストールしました。
ここでは、日本語を含むテーブル・カラム・クエリ等の扱いと、その他、気になる点について追試してみます。
対象は引き続き MS 版 ODBC ドライバです。
1. 追試用プロジェクトの準備
導入編で nanodbc をインストールした状態が前提です。
その続きとして、別途 nanodbc の共有ライブラリを利用する環境を整えます。
既に work ディレクトリが存在するとして、新たに my_test ディレクトリを作成します。
$ cd ~/work
$ mkdir my_test
$ cd my_test
my_test ディレクトリに、CMakeLists.txt
とmain.cpp
を作成します。
・CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(my_test CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(nanodbc REQUIRED)
add_executable(a.out main.cpp)
target_compile_options(a.out PRIVATE -g -Wall)
target_link_libraries(a.out PRIVATE nanodbc)
・main.cpp
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <nanodbc/nanodbc.h>
int main()
{
std::locale::global(std::locale(""));
try
{
nanodbc::connection conn("Driver={ODBC Driver 17 for SQL Server};Server=localhost;UID=sa;PWD=abcd1234$;Database=my_test_db");
std::cout << "確認 #1" << std::endl;
{
nanodbc::result rs1 = nanodbc::execute(conn, NANODBC_TEXT("SELECT '社員コード'"));
rs1.next();
std::cout << "varchar として処理 : [" << rs1.get<std::string>(0) << "]" << std::endl;
nanodbc::result rs2 = nanodbc::execute(conn, NANODBC_TEXT("SELECT N'社員コード'"));
rs2.next();
std::cout << "nvarchar として処理 : [" << rs2.get<std::string>(0) << "]" << std::endl;
nanodbc::result rs3 = nanodbc::execute(conn, NANODBC_TEXT("SELECT NULL"));
rs3.next();
std::cout << "NULL値を取得 : " << rs3.get<std::string>(0, "< null >を取得") << std::endl;
}
std::cout << "確認 #2" << std::endl;
{
nanodbc::execute(conn, NANODBC_TEXT("DROP TABLE IF EXISTS 社員"));
nanodbc::execute(conn, NANODBC_TEXT(
"CREATE TABLE 社員 (コード int, 氏名 nvarchar(40), 入社日 date, 備考 nvarchar(max))"
));
std::vector<std::vector<std::string>> rec = {
{ "210", "成宮 真紀", "1991-04-01" },
{ "110", "加藤 泰江", "1990-04-01" },
{ "105", "森上 偉久馬", "1990-04-01" },
{ "304", "山本 雅治", "1989-04-01" },
{ "307", "小川 さよ子", "1987-04-01" },
{ "305", "青木 俊之", "1988-04-01" },
{ "207", "松沢 誠一", "1994-04-01" },
{ "107", "葛城 孝史", "1991-09-01" },
{ "204", "川村 匡", "1990-04-01" },
};
nanodbc::statement stmt(conn);
nanodbc::prepare(stmt, NANODBC_TEXT("INSERT INTO 社員 (コード,氏名,入社日) VALUES (?,?,?)"));
for (const std::vector<std::string>& param : rec) {
stmt.bind(0, param.at(0).c_str());
stmt.bind(1, param.at(1).c_str());
stmt.bind(2, param.at(2).c_str());
nanodbc::execute(stmt);
}
nanodbc::string qry(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 ORDER BY コード"));
nanodbc::result rs = nanodbc::execute(conn, qry);
std::cout << "結果 " << rs.affected_rows() << " 行, rowset size: " << rs.rowset_size() << std::endl;
std::cout << rs.column_name(0) << " | " << rs.column_name(1) << " | " << rs.column_name(2) << std::endl;
for (const auto& row : rs) {
auto val = row.get<int>(0);
auto name = row.get<nanodbc::string>(1);
auto d = row.get<nanodbc::date>(2);
std::cout << val << " | " << name << " | " << d.year << "." << d.month << "." << d.day << std::endl;
}
std::cout << "結果 " << rs.affected_rows() << " 行, rowset size: " << rs.rowset_size() << std::endl;
}
std::cout << "確認 #3" << std::endl;
{
nanodbc::statement stmt(conn);
nanodbc::prepare(stmt, NANODBC_TEXT("UPDATE 社員 SET 備考=? WHERE コード=?"));
std::string memo1 = R"(【自己紹介】
私の故郷は、富士五湖のひとつである本栖湖のほとりにあり、実家はキャンプ場を経営しています。
年中無休なので、子供の頃からよく手伝わされました。
特に夏のシーズンは忙しく、夏休みに家族で旅行に行った記憶はありません。
)";
stmt.bind(0, memo1.c_str());
stmt.bind(1, "210");
nanodbc::execute(stmt);
std::list<std::string> sl;
sl.push_back("【自己紹介】\n");
sl.push_back("私は、スポーツが好きで、特にサッカー" u8"\U000026BD\U0001F945" "は見るのもするのも夢中になります。\n");
sl.push_back("\n");
sl.push_back("並行して、家が以前、エレクトーン" u8"\U0001F3B9" "の教室を開いていたので、楽器もよく演奏したりしていました。\n");
std::string memo2;
for (const auto& line : sl) {
memo2 += line;
}
const int val = 110;
stmt.bind(0, memo2.c_str());
stmt.bind(1, &val);
nanodbc::execute(stmt);
nanodbc::string qry(NANODBC_TEXT("SELECT コード,氏名,備考 FROM 社員 WHERE 備考 IS NOT NULL"));
nanodbc::result rs = nanodbc::execute(conn, qry);
for (const auto& row : rs) {
std::cout << row.get<int>("コード") << " : " << row.get<nanodbc::string>("氏名") << std::endl;
auto prof = row.get<nanodbc::string>("備考");
std::cout << ((prof.length() > 0) ? prof : "なし") << std::endl;
}
}
std::cout << "確認 #4" << std::endl;
{
nanodbc::statement stmt(conn);
nanodbc::string sql_text(NANODBC_TEXT("SELECT コード,氏名,入社日 FROM 社員 WHERE 氏名 LIKE ? OR 氏名 LIKE ?"));
nanodbc::prepare(stmt, sql_text);
stmt.bind(0, "%泰%");
stmt.bind(1, "%川%");
nanodbc::result rs = nanodbc::execute(stmt);
struct temp {
int id;
nanodbc::string name;
nanodbc::string joined;
};
std::vector<temp> target;
for (const auto& row : rs) {
target.push_back({
row.get<int>("コード"),
row.get<nanodbc::string>("氏名"),
row.get<nanodbc::string>("入社日"),
});
}
std::cout << "1回目" << std::endl;
for (const auto& d : target) {
std::cout << d.id << " | " << d.name << " | " << d.joined << std::endl;
}
std::cout << "2回目" << std::endl;
for (int i = target.size(); i > 0; i--) {
const auto& d = target.at(i - 1);
std::cout << d.id << " | " << d.name << " | " << d.joined << std::endl;
}
}
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
このソースコードは、私が Qt5 で書いたテストコードを部分的に流用してます。
その為、一部に不自然な記述もありますが、今回の追試には影響ないはずです。
(3) ビルド用ディレクトリの準備
my_test ディレクトリにビルド用のディレクトリを作成します。
$ pwd
/home/dareka/work/my_test
$ mkdir build
$ cd build
以降の操作は、このディレクトリで作業します。
2. ビルドと実行
まずはプロジェクトをコンパイルして実行ファイルを生成します。
$ cmake ..
$ make
(2) 実行
ビルドが成功すると、a.out
が生成されるので、これを実行します。
テスト編と同様に、実行時の接続先データベースは 'my_test_db' です。
$ ./a.out
3. 実行結果の確認
確認したい事を 4 つのブロックに分けて処理しました。
結果は、すべて想定内・期待通り、でした。
(1) 単一の値を返すクエリの確認
std::cout << "確認 #1" << std::endl;
以降のブロックです。
SELECT 文に値を直接指定して nanodbc::result で受けてます。
N' ' で括られた日本語を含む文字列リテラルは暗黙に nvarchar として扱われ、期待通りに動作します。一方、' ' で括られた方は暗黙に varchar として扱われ、文字化けしているように見えます。
(2) 日本語でカラムやテーブルを定義してローを挿入する
std::cout << "確認 #2" << std::endl;
以降のブロックです。
まず、日本語を含むテーブルを作成します。
そこに、日本語を含む値をプレースホルダにバインドして INSERT してます。
全件 SELECT して表示する際、affected_row() の変化にも注目してみました。
(3) nvarchar(max)型で日本語や絵文字を含むテキストを扱う
std::cout << "確認 #3" << std::endl;
以降のブロックです。
生文字列リテラルとstd::stringを連結したテキストデータが期待通りに設定・再取得できる事を確認してます。
敢えて異なる方法でバインドして、それぞれ正常に処理されてます。
Ubuntu では絵文字を含めて期待通りに処理されました。
Windows や macOS 等が混在した状態では試してません。
(4) LIKE 演算子へのパラメータと結果セットの再利用
std::cout << "確認 #4" << std::endl;
以降のブロックです。
LIKE 演算子に対する日本語を含むパラメータバインドのテストが無かったので、試してみました。
結果セットを構造体の動的配列に退避して、値の再利用も試してます。
4. まとめ
とりあえず気になった点については、自分なりに納得できる結果でした。
nanodbc で用意されているテストは網羅する範囲が広く、標準のテストに追加する仕組みも用意されているので、とても助かります。
今回は日本語が絡む為、別途プロジェクトを用意しての追試です。
実際のところ、テーブル名やカラム名に日本語を採用した現場に遭遇した事はありません。それでも、クエリと結果セットに日本語が含まれる可能性が高い為、今回のような日本語を多用するケースを敢えて検証しました。
UTF-8 にしろ UTF-16 にしろ、現在の文字コードの混沌に立ち向かえる程、私は強くないですが、せめて自分の足元くらいは確認しておきたいと思ってます。
記事のリスト
ついつい熱が入った紹介になりましたが、本当に便利だと思います。
私の場合、C++ で ODBC を扱うコードを書くなら Qt5 を最優先にしてましたが、これなら nanodbc も十分選択肢に入ります。