xDevAPIラッパー編

xDevAPI

チェケッラッチョーのラッパーではないです。サランラップ、クレラップの方のラッパーですね。
今回は少しC++寄りな話になります。

ラッパー

xDevAPIを外側を包んで自分の好みの色を付けましょうというテーマになります。
なんで?(?_?)
って思われるかもしれないですが、これは前の章で話した、テーブルから取得した値を取り出すのに配列番号しか使えないのが「気に入らない」からです。
これ使えないのはC++だけっぽいんですよね…なんでだろう…
THEコーディング編」のをそのまま張り付けますけど、
C++は以下のようにしかコーディングできない…これを

std::cout << row[0] << ":" << row[1] << std::endl;

以下のイメージでテーブルの項目で書きたい!って願望です。

std::cout << row["id"] << ":" << row["name"] << std::endl;

どうやって実現するかをちょっと説明してみます。

実現の方法

xDevAPIにテーブル項目を取ってくる関数があります。以下ですね。

const Columns & getColumns()const

でこれで項目を取ってきて、mapにKEYに項目名、VALUEに配列番号を設定してブッコンで項目名を指定して取り出すときに、以下のイメージにするって感じですね。

std::cout << row[map("id")] << ":" << row[map("name")] << std::endl;

まぁ上記の感じだとカッコウが悪いので…その辺は上手く作ってあげないといかんとですね…
今回ラッパーするClassは以下のものになります。

xDevAPI Class名ラッパー Class名Class内容
SessionSessionMySQLとの接続をするクラス
ClientClientSessionクラスを複数プールして保持するクラス
SqlResultSqlResultSQLの結果取得クラス
RowRowSQLの結果からレコードを取得するクラス。

moveとcopy

ラッパーを説明する前に少し脱線してC++のmoveとcopyについて触れておきます。
と・・・言うのも、xDevAPIのClassは基本moveで、copyは不許可のようです。
例えば「THEコーディング編」のコードに以下のように、Sessionのcopyを追加してビルドすると…

      1 #include <iostream>
      2 #include <list>
      3 #include <mysqlx/xdevapi.h>
      4
      5 using namespace ::mysqlx;
      6
      7 int main(int argc, const char* argv[])
      8 {
      9     std::string url = "mysqlx://root:password@172.17.0.2:33060";
     10     mysqlx::Session mySession(url);
     11     mysqlx::Session tmpsession = mySession; ★コピーを試みる
     12     mySession.sql("USE test_db").execute();
     13     mysqlx::SqlResult myResult = mySession.sql("SELECT * FROM mytest;").execute();
     14     std::cout << "row count:"<< myResult.count() << std::endl;
     15     std::list<mysqlx::Row> rows = myResult.fetchAll();
     16     for(mysqlx::Row row:rows)
     17     {
     18         std::cout << row[0] << ":" << row[1] << std::endl;
     19     }
     20 }

以下のようにエラーになります・・・

# cmake3 --build build
Scanning dependencies of target test_conncpp
[ 50%] Building CXX object CMakeFiles/test_conncpp.dir/main.cpp.o
/root/work/BUILD/test_blog/main.cpp: In function ‘int main(int, const char**)’:
/root/work/BUILD/test_blog/main.cpp:13:34: error: use of deleted function ‘mysqlx::abi2::r0::Session::Session(const mysqlx::abi2::r0::Session&)’
   13 |     mysqlx::Session tmpSession = mySession;
      |                                  ^~~~~~~~~
In file included from /root/work/BUILD/test_blog/main.cpp:3:
/usr/local/mysql/connector-c++-8.2.0/include/mysqlx/xdevapi.h:1676:7: note: ‘mysqlx::abi2::r0::Session::Session(const mysqlx::abi2::r0::Session&)’ is implicitly declared as deleted because ‘mysqlx::abi2::r0::Sessio ’ declares a move constructor or move assignment operator
 1676 | class Session
      |       ^~~~~~~
gmake[2]: *** [CMakeFiles/test_conncpp.dir/main.cpp.o] エラー 1
gmake[1]: *** [CMakeFiles/test_conncpp.dir/all] エラー 2
gmake: *** [all] エラー 2

これは、Sessionクラスではコピーコンストラクタがdeleteに定義されているからです。
grepで調べると以下の感じで・・。session.hで定義されてます。

devapi/detail/session.h:313:  Session_detail(const Session_detail&) = delete;
devapi/detail/session.h:314:  Session_detail& operator=(const Session_detail&) = delete;

な・の・で、ラッパーについても、基本moveで設計しないといけない・・・と言うことなります。
ので・・・C++のcopyとmoveの知識が必要になっていきます。
これを説明するのはメンドイので・・・色々調べた結果で、説明してくれているサイトのリンクを張っておきます・・・ので、どんなものかチラッと見てみると良いかと思います。
C++ のムーブを理解する
第36回目 最後の特殊メンバ関数ムーブ・コンストラクタ
第37回目 ムーブの使いどころ

Sessionラッパー

まずは、ラッパー関数を取りまとめるヘッダー名を先に決めちゃいます。
名前なのでなんでもよかったんですけけど以下にしました。
xdevapipp.h
Sessionは以下の感じになります。まずは、コンストラクタのあたりだけ限定的に・・・。

xdevapipp.h
 1 namespace xdevapipp{
 2 class Session
 3 {
 4 public:
 5     mysqlx::Session _origin_session;
 6     Session(mysqlx::Session tmpsession):_origin_session(std::move(tmpsession)){}
 7     ~Session(){}
 8 };
 9 }

5行目は、xDevAPIのSessionクラスのオブジェクトを保持する変数です。
6行目は、moveコンストラクタになります。
実際に使うときは以下のようなコードになります。本家xDevAPIの時の
mysqlx::Session mySession(url);
とほとんど変わることなくコーディングできるかと思います。

 1 std::string url = "mysqlx://root:password@172.17.0.2:33060";
 2 xdevapipp::Session mySession(url);

現状のラッパークラスだと実際に使うには機能がたりないので使えないので・・・
SQL実行のメンバ関数(sqlexec)を追加します。あとSessionSettingsクラスを使って初期化できたら便利かなという想いがあったのでSessionSettingsクラス用のコンストラクタも追加して最終的には以下の感じなります。
追加したの7行目と9~16行目になります。

xdevapipp.h
 1 namespace xdevapipp{
 2 class Session
 3 {
 4 public:
 5     mysqlx::Session _origin_session;
 6     Session(mysqlx::Session tmpsession):_origin_session(std::move(tmpsession)){}
 7     Session(mysqlx::SessionSettings tmpsettings):_origin_session(mysqlx::Session(tmpsettings)){}
 8     ~Session(){}
 9     SqlResult sqlexec(std::string sql){
10         SqlResult tmpresult;
11         mysqlx::SqlResult tmporg_result = _origin_session.sql(sql).execute();
12         if(tmporg_result.hasData()){
13             tmpresult.set(std::move(tmporg_result));
14         }
15         return tmpresult;
16     }
17 };
18 }

実際使うときは以下のようなコードになります。sql実行のところは、本家xDevAPIと少し違ったコーディングになっちゃいますが…その点はご了承下さい。

 1 std::string url = "mysqlx://root:password@172.17.0.2:33060";
 2 xdevapipp::Session mySession(url);
 3 xdevapipp::SqlResult myResult = mySession.sqlexec("SELECT * FROM mytest;");

Sessionクラスのラッパーはこんなところですね。

SqlResultクラスのラッパー

今回のポイントとなるSqlResultクラスが登場しちゃったの、こちらを説明します。

 1 namespace xdevapipp{
 2 class SqlResult
 3 {
 4 public:
 5     std::map<std::string,int> _column_index;
 6     mysqlx::SqlResult _origin_result;
 7     SqlResult(){}
 8     ~SqlResult(){}
 9     SqlResult(SqlResult&& src) : _column_index(src._column_index),_origin_result(std::move(src._origin_result)){}
10     SqlResult& operator=(SqlResult&& src) {
11         if( &src == this ) return *this;
12         _origin_result = std::move(src._origin_result);
13         _column_index = src._column_index;
14         return *this;
15     }
16     void set(mysqlx::SqlResult tmpresult){
17         _origin_result=std::move(tmpresult);
18         const mysqlx::Columns &columns = _origin_result.getColumns();
19         for( unsigned index=0;index < _origin_result.getColumnCount(); index++)
20         {
21             _column_index[columns[index].getColumnName()]=index;
22         }
23     }
24     Row fetchOne(){
25         Row tmprow;
26         tmprow._column_index=_column_index;
27         tmprow._origin_row=_origin_result.fetchOne();
28         return tmprow;
29     }
30     mysqlx::row_count_t count(){ return _origin_result.count();}
31 };
32 }

5行目:std::map<std::string,int> _column_index;
先に説明した通り、項目とINDEX番号を紐づけるためのmapクラスの変数です。
6行目:mysqlx::SqlResult _origin_result;
xDevAPIのSqlResultクラスオブジェクトを保持するための変数です。
7行目:SqlResult(){}
普通のコンストラクタ
8行目:~SqlResult(){}
デストラクタ
9行目:SqlResult(SqlResult&& src) :…省略
moveコンストラクタ
10~15行目:SqlResult& operator=(SqlResult&& src)
ムーブ代入演算子
16~23行目:void set(mysqlx::SqlResult tmpresult)
こちらで、5行目の_column_indexを作ります。
 17行目:_origin_result=std::move(tmpresult);でまずはオリジナルのSqlResultクラスをmove
 18行目:const mysqlx::Columns &columns = _origin_result.getColumns();でカラムを取得
 19~22行目:カラム数分ループして、項目とINDEX番号を_column_index(mapクラス)に設定
の流れです。
24~29行目は、SQL実行で取得したレコードを1レコードずつ取得する関数です。
これはfetchOneのみ今回実装していてfetchAllは実装していません。
なんで・・・?(?_?)
ただ単につくるのが面倒だった・・・だけどすけど・・・興味がある人は実装してみると良いかと思います。
ここはなんか、説明が雑になりましたが・・・こんなところかな・・・

Rowクラスラッパー

Rowクラスも出てきたのでこちらも・・・

 1 namespace xdevapipp{
 2 class Row
 3 {
 4 public:
 5     Row(){}
 6     ~Row(){}
 7     Row(Row&& src):
 8         _column_index(src._column_index),
 9         _origin_row(std::move(src._origin_row)){}
10     Row& operator=(Row&& src) {
11         if( &src == this ) return *this;
12         _origin_row = std::move(src._origin_row);
13         _column_index = src._column_index;
14         return *this;
15     }
16     auto operator [] (const std::string key){
17         return _origin_row[_column_index[key]];
18     }
19     auto operator [] (const char* key){
20         std::string tmpkey(key);
21         return _origin_row[_column_index[tmpkey]];
22     }
23     bool isNull() const {
24         bool tmpresult=false;
25         if(_origin_row)tmpresult=true;
26         return tmpresult;
27     }
28     operator bool() const { return isNull(); }
29     std::map<std::string,int> _column_index;
30     mysqlx::Row _origin_row;
31 };
32 }

こちらは、ポイントだけ・・・
7~9行目はmoveコンストラクタなんですが、_colum_indexについては、コピーしてます。
最初はラッパーのSqlResultクラスで作るんですけど、これをmoveしてしまうと、SqlResultクラス側で使えなくなっちゃうので、それは困る!ということで、コピーすることにしました。

16~22行目は、[]演算子のオーバーロードですね。string型とchar*型について用意しました。
row[map(“id”)]こうコーディングするのはかっこ悪いので、row[“id”]このようにコーディングできるようになります。

これで必要なものは大体登場しましたね。
個人的にプール機能も使いたかったので、プール機能用のクラスClientクラスも含めて、xdevapipp.hの全体は以下になります。

xdevapipp.h
  1 #ifndef X_DEVAPIPP_H
  2 #define X_DEVAPIPP_H
  3 #include <string>
  4 #include <mysqlx/xdevapi.h>
  5 #include <map>
  6
  7 namespace xdevapipp{
  8
  9 class Row
 10 {
 11 public:
 12     Row(){}
 13     ~Row(){}
 14     Row(Row&& src):
 15         _column_index(src._column_index),
 16         _origin_row(std::move(src._origin_row)){}
 17     Row& operator=(Row&& src) {
 18         if( &src == this ) return *this;
 19         _origin_row = std::move(src._origin_row);
 20         _column_index = src._column_index;
 21         return *this;
 22     }
 23     auto operator [] (const std::string key){
 24         return _origin_row[_column_index[key]];
 25     }
 26     auto operator [] (const char* key){
 27         std::string tmpkey(key);
 28         return _origin_row[_column_index[tmpkey]];
 29     }
 30     bool isNull() const {
 31         bool tmpresult=false;
 32         if(_origin_row)tmpresult=true;
 33         return tmpresult;
 34     }
 35     operator bool() const { return isNull(); }
 36     std::map<std::string,int> _column_index;
 37     mysqlx::Row _origin_row;
 38 };
 39 class SqlResult
 40 {
 41 public:
 42     std::map<std::string,int> _column_index;
 43     mysqlx::SqlResult _origin_result;
 44     SqlResult(){}
 45     ~SqlResult(){}
 46     SqlResult(SqlResult&& src):
 47         _column_index(src._column_index),
 48         _origin_result(std::move(src._origin_result)){}
 49     SqlResult& operator=(SqlResult&& src) {
 50         if( &src == this ) return *this;
 51         _origin_result = std::move(src._origin_result);
 52         _column_index = src._column_index;
 53         return *this;
 54     }
 55     void set(mysqlx::SqlResult tmpresult){
 56         _origin_result=std::move(tmpresult);
 57         const mysqlx::Columns &columns = _origin_result.getColumns();
 58         for( unsigned index=0;index < _origin_result.getColumnCount(); index++)
 59         {
 60             _column_index[columns[index].getColumnName()]=index;
 61         }
 62     }
 63     Row fetchOne(){
 64         Row tmprow;
 65         tmprow._column_index=_column_index;
 66         tmprow._origin_row=_origin_result.fetchOne();
 67         return tmprow;
 68     }
 69     mysqlx::row_count_t count(){ return _origin_result.count();}
 70 };
 71 class Session
 72 {
 73 public:
 74     mysqlx::Session _origin_session;
 75     Session(mysqlx::Session tmpsession):
 76         _origin_session(std::move(tmpsession)){}
 77     Session(mysqlx::SessionSettings tmpsettings):
 78         _origin_session(mysqlx::Session(tmpsettings)){}
 79     ~Session(){}
 80     SqlResult sqlexec(std::string sql){
 81         SqlResult tmpresult;
 82         mysqlx::SqlResult tmporg_result = _origin_session.sql(sql).execute();
 83         if(tmporg_result.hasData()){
 84             tmpresult.set(std::move(tmporg_result));
 85         }
 86         return tmpresult;
 87     }
 88 };
 89 class Client
 90 {
 91 public:
 92     mysqlx::Client _origin_client;
 93     Client(mysqlx::Client tmpclient):
 94         _origin_client(std::move(tmpclient)){}
 95     Client(mysqlx::ClientSettings tmpsettings):
 96         _origin_client(mysqlx::Client(tmpsettings)){}
 97     ~Client(){}
 98     xdevapipp::Session getSession(){
 99         mysqlx::Session tmporgsession=_origin_client.getSession();
100         return xdevapipp::Session(std::move(tmporgsession));
101     }
102 };
103 }
104 #endif

使ってみる

せっかく作ったので使ってみましょうって企画です。
まずはディレクトリの構成からです。
xdevapipp.hは上のソースを張り付けて作ってもらって、あとはCMakeLists.txtとmain.cppを作って実行って流れになります。
DBについては、「THEコーディング編」でつかったのをそのまま使えればと思っています。

任意のディレクトリ
├ CMakeLists.txt
├ xdevapipp.h
└ main.cpp

CMakeList.txtも「THEコーディング編」で使ったものをそのまま使えばOKです。
使う環境に合わせてPATHなどをカスタマイズして下さい。

CMakeList.txt
 1 make_minimum_required(VERSION 3.13)
 2 set(CMAKE_CXX_STANDARD 17)
 3 set(CMAKE_C_COMPILER "/opt/rh/devtoolset-11/root/usr/bin/gcc")
 4 set(CMAKE_CXX_COMPILER "/opt/rh/devtoolset-11/root/usr/bin/g++")
 5 project(test_cmake CXX)
 6 set(MYSQL_CONCPP_DIR "/usr/local/mysql/connector-c++-8.2.0")
 7 set(MYSQL_CONNECTOR_CPP_LIBS_DIR "/usr/local/mysql/connector-c++-8.2.0/lib64/debug")
 8 set(MYSQL_CONNECTOR_INCLUDE_DIR "/usr/local/mysql/connector-c++-8.2.0/include")
 9 find_library(
10         MYSQL_CONN_CPP_LIBS
11         NAMES libmysqlcppconn8-static.a
12         HINTS "${MYSQL_CONNECTOR_CPP_LIBS_DIR}"
13         NO_DEFAULT_PATH
14 )
15 add_executable(test_conncpp main.cpp)
16 target_compile_options(test_conncpp PUBLIC -O2 -Wall -g)
17 target_compile_features(test_conncpp PUBLIC cxx_std_17)
18 target_compile_definitions(test_conncpp PUBLIC -DSTATIC_CONCPP)
19 target_include_directories(test_conncpp PUBLIC ${MYSQL_CONNECTOR_INCLUDE_DIR})
20 target_link_libraries(test_conncpp ssl crypto pthread resolv ${MYSQL_CONN_CPP_LIBS})

データベースも先言った通り同じです。
コンテナイメージ作成※実施済みならスキップコンテナ起動から実施。

sudo docker build ./ -t testmysql:0.0

コンテナ起動

sudo docker run -it -d --privileged --rm --name testmysql testmysql:0.0

コンテナログイン

sudo docker exec -it testmysql /bin/bash

コンテナ上でMySQLログイン

bash-4.4# mysql -u root -p
Enter password:←"password"と入力してEnter

ログインできたらまず使用DBスキーマを選択

mysql> use test_db

テーブルの作成

mysql> create table mytest (id int, name varchar(10));

レコード追加

mysql> INSERT INTO mytest (id,name) VALUES (1,'taro'),(2,'hanako');

テーブル内容の確認

mysql> SELECT * FROM mytest;
+------+--------+
| id   | name   |
+------+--------+
|    1 | taro   |
|    2 | hanako |
+------+--------+
2 rows in set (0.00 sec)

これでテーブル準備はOK
main.cppのソースは以下になります。
「THEコーディング編」と大体同じ内容なので細かい説明は割愛します。
15行目:settings.set(SessionOption::DB,”test_db”);
ここは接続時に使用するDBスキーマを事前に指定しています。
接続したあと、use test_dbってやる手間を省いていますね。
19~24行目:はテーブルからレコードを取得する部分です。
前回はfetchAllを使いましたけど、今回はfetchOneを使っています。

main.cpp
 1 #include <iostream>
 2 #include <list>
 3 #include <mysqlx/xdevapi.h>
 4 #include "xdevapipp.h"
 5
 6 using namespace ::mysqlx;
 7 using namespace ::xdevapipp;
 8
 9 int main(int argc, const char* argv[])
10 {
11     int ret=0;
12     try{
13         std::string url = "mysqlx://root:password@172.17.0.2:33060";
14         mysqlx::SessionSettings settings(url);
15         settings.set(SessionOption::DB,"test_db");
16         xdevapipp::Session mySession(settings);
17         xdevapipp::SqlResult myResult = mySession.sqlexec("SELECT * FROM mytest;");
18         std::cout << "row count:"<< myResult.count() << std::endl;
19         xdevapipp::Row row = myResult.fetchOne();
20         while(row)
21         {
22             std::cout << "id:" << row["id"] << "name:" << row["name"] << std::endl;
23             row = myResult.fetchOne();
24         }
25     }
26     catch (const mysqlx::Error &err)
27     {
28         std::cout <<"ERROR: " <<err << std::endl;
29         ret=-1;
30     }
31     catch (std::exception &ex)
32     {
33         std::cout <<"STD EXCEPTION: " <<ex.what() <<std::endl;
34         ret=-2;
35     }
36     catch (const char *ex)
37     {
38         std::cout <<"EXCEPTION: " <<ex <<std::endl;
39         ret=-3;
40     }
41     return ret;
42 }

最後はビルドです。以下の手順で実施です。

ビルド準備(※最初の1回のみ実施)
# cmake3 -S . -B build
ビルド実行
# cmake3 --build build

あとは実行のみ
実行はbuildディレクトリ下に、「test_conncpp」って言うのができているのでこれを実行ですね。

# ./test_conncpp
row count:2
id:1name:taro
id:2name:hanako

こんな感じで出力されるはずです。
めでたしめでたしパチパチ(´_ゝ`)
色々作業が終わったらdockerを止めて終了です。

# sudo docker stop testmysql

これでラッパー編完了です!
次はマルチDBアクセス編をやってみたいと思います。

コメント

タイトルとURLをコピーしました