それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

MySQL Connector/C++ を使ってセッションを気合で作る

はじめに

最近、公私共に MySQL Server と戯れることが多いが何らかの手順をクソ長いシェルスクリプトにして実行することに嫌気が指したので CLI ツールの自作を始めた。
MySQL そのものの実装を読むときも多いので、そろそろ慣れようと思って C++ で書いたが運の尽き。MySQL Connector/C++ が曲者中の曲者で、セッションを作るところすら辿り着くまで時間がかかったのでメモがてらまとめる。

環境

  • Ubuntu 22.04 (実際には PopOS)
  • g++ 11.3.0-1ubuntu1~22.04
  • cmake 3.22.1
  • MySQL 8.0.30 (source code build)

8.0.16 あたりで conncpp も少し変わったみたいだが面倒なので一切考慮しない。

ここでやりたいこと

MySQL :: MySQL Connector/C++ 8.0 Developer Guide :: 5.1 Building Connector/C++ Applications: General Considerations
公式ドキュメントによると

#include <mysqlx/xdevapi.h>

を引っ張ることができれば大体のことを実装できそうな雰囲気があるので、そこにあるものを使ってセッションを作る。

Connector/C++ をインストールする

この時点ですでに曲者である。
MySQL :: Download MySQL Connector/C++ (Archived Versions)
公式で配布されている deb パッケージから使いたいバージョンや OS などを指定して deb パッケージを探しインストールするが
自分の場合 libmysqlcppconn8-2_8.0.30-1ubuntu22.04_amd64.deb をインストールする時に mysql-community-client-plugins が入っていないと怒られる。

そんなものどこにも書いてない気がするが、これが入らないことには始まらないので
MySQL :: Download MySQL Community Server
公式から mysql-community-client-plugins_8.0.30-1ubuntu22.04_amd64.deb を探し出してインストールした。その後 libmysqlcppconn* を全部インストールする。

諸々のパスを確認する

libmysqlcppconn8* の場所を確認する。後でリンクするので必要になる。

$ dpkg -L libmysqlcppconn8-2
/.
/usr
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu/libmysqlcppconn8.so.2.8.0.30
/usr/share
/usr/share/doc
/usr/share/doc/libmysqlcppconn8-2
/usr/share/doc/libmysqlcppconn8-2/INFO_BIN
/usr/share/doc/libmysqlcppconn8-2/INFO_SRC
/usr/share/doc/libmysqlcppconn8-2/LICENSE.txt.gz
/usr/share/doc/libmysqlcppconn8-2/README.txt
/usr/share/doc/libmysqlcppconn8-2/changelog.Debian.gz
/usr/share/doc/libmysqlcppconn8-2/copyright
/usr/share/lintian
/usr/share/lintian/overrides
/usr/share/lintian/overrides/libmysqlcppconn8-2
/usr/lib/x86_64-linux-gnu/libmysqlcppconn8.so.2

/usr/lib/x86_64-linux-gnu 以下に入っているらしい。

次に libmysqlcppconn-dev でインストールした諸々のパスを確認する。include dir を指定するのに後で使う。

$  dpkg -L libmysqlcppconn-dev
/usr/include/mysql-cppconn-8
/usr/include/mysql-cppconn-8/jdbc
/usr/include/mysql-cppconn-8/jdbc/cppconn
/usr/include/mysql-cppconn-8/jdbc/cppconn/build_config.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/callback.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/config.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/connection.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/datatype.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/driver.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/exception.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/metadata.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/parameter_metadata.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/prepared_statement.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/resultset.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/resultset_metadata.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/sqlstring.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/statement.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/variant.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/version_info.h
/usr/include/mysql-cppconn-8/jdbc/cppconn/warning.h
/usr/include/mysql-cppconn-8/jdbc/mysql_connection.h
/usr/include/mysql-cppconn-8/jdbc/mysql_driver.h
/usr/include/mysql-cppconn-8/jdbc/mysql_error.h
/usr/include/mysql-cppconn-8/mysql
/usr/include/mysql-cppconn-8/mysql/jdbc.h
/usr/include/mysql-cppconn-8/mysqlx
/usr/include/mysql-cppconn-8/mysqlx/common
/usr/include/mysql-cppconn-8/mysqlx/common/api.h
/usr/include/mysql-cppconn-8/mysqlx/common/error.h
/usr/include/mysql-cppconn-8/mysqlx/common/op_if.h
/usr/include/mysql-cppconn-8/mysqlx/common/settings.h
/usr/include/mysql-cppconn-8/mysqlx/common/util.h
/usr/include/mysql-cppconn-8/mysqlx/common/value.h
/usr/include/mysql-cppconn-8/mysqlx/common.h
/usr/include/mysql-cppconn-8/mysqlx/common_constants.h
/usr/include/mysql-cppconn-8/mysqlx/devapi
/usr/include/mysql-cppconn-8/mysqlx/devapi/collations.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/collection_crud.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/common.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/crud.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail/crud.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail/error.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail/result.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail/row.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail/session.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/detail/settings.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/document.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/error.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/executable.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/mysql_charsets.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/mysql_collations.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/result.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/row.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/settings.h
/usr/include/mysql-cppconn-8/mysqlx/devapi/table_crud.h
/usr/include/mysql-cppconn-8/mysqlx/version_info.h
/usr/include/mysql-cppconn-8/mysqlx/xapi.h
/usr/include/mysql-cppconn-8/mysqlx/xdevapi.h

どうやら /usr/include/mysql-cppconn-8 以下に入っているらしい。

簡単なサンプル

最も簡単なパターンでセッションの確立を試みる。

#include <mysqlx/xdevapi.h>

int main() {
        mysqlx::SessionSettings session_settings = mysqlx::SessionSettings("localhost", 33060, "root", "root", "test");
        mysqlx::Session session(session_settings);
        return 0;
}


コンパイルして実行する。

$ g++ -std=c++11 -I/usr/include/mysql-cppconn-8/ -L/usr/lib/x86_64-linux-gnu main.cpp -lmysqlcppconn8 -o main
$ ./main
$ 

何もエラーがでなければ成功である。

CDK Error: unexpected message

これが曲者ナンバーワンである。
コンパイルして、実行するとこのメッセージが出力される場合がある。

$ ./main 
terminate called after throwing an instance of 'mysqlx::abi2::r0::Error'
  what():  CDK Error: unexpected message

これだけでは何が原因か全くわからないが、mysqlx plugin がないか動いていない場合または x protocol を利用しているのでポートが 3306 ではなく 33060 にしていない場合に起こることがわかった。
これから推測するにおそらく認証等々以前に接続に必要な前提が欠けている場合に起こるっぽい。

参考:
使用C++连接MySql8.0提示错误:CDK Error: unexpected message_cdk error unexpected message_icky_1024的博客-CSDN博客
MySQL :: CDK Error: unexpected message
c++ - X DevAPI mysqlx::Session() over linux socket fails with “CDK Error: unexpected message” - Stack Overflow

コンパイルできない

曲者ナンバーツーである。
CMake とかでイケイケビルドをしようとすると、以下のようなエラーに遭遇する場合がある。

$ make
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: warning: relocation against `_ZTVN6mysqlx4abi22r05DbDocE' in read-only section `.text.startup'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: in function `mysqlx::abi2::r0::Value::~Value()':
main.cpp:(.text._ZN6mysqlx4abi22r05ValueD1Ev[_ZN6mysqlx4abi22r05ValueD1Ev]+0x49): undefined reference to `vtable for mysqlx::abi2::r0::DbDoc'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: in function `mysqlx::abi2::r0::internal::Warning_detail::print(std::ostream&) const':
main.cpp:(.text._ZNK6mysqlx4abi22r08internal14Warning_detail5printERSo[_ZNK6mysqlx4abi22r08internal14Warning_detail5printERSo]+0x75): undefined reference to `mysqlx::abi2::r0::string::Impl::to_utf8[abi:cxx11](mysqlx::abi2::r0::string const&)'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: in function `virtual thunk to mysqlx::abi2::r0::internal::Warning_detail::print(std::ostream&) const':
main.cpp:(.text._ZNK6mysqlx4abi22r08internal14Warning_detail5printERSo[_ZNK6mysqlx4abi22r08internal14Warning_detail5printERSo]+0x38a): undefined reference to `mysqlx::abi2::r0::string::Impl::to_utf8[abi:cxx11](mysqlx::abi2::r0::string const&)'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: in function `mysqlx::abi2::r0::Value::print(std::ostream&) const':
main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0xd8): undefined reference to `mysqlx::abi2::r0::DbDoc::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x17f): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x217): undefined reference to `mysqlx::abi2::r0::DbDoc::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x2b7): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x34d): undefined reference to `mysqlx::abi2::r0::DbDoc::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x3df): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x463): undefined reference to `mysqlx::abi2::r0::DbDoc::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x4e7): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x44): undefined reference to `mysqlx::abi2::r0::DbDoc::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x6a7): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: in function `virtual thunk to mysqlx::abi2::r0::Value::print(std::ostream&) const':
main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x6e7): undefined reference to `mysqlx::abi2::r0::DbDoc::print(std::ostream&) const'
/usr/bin/ld: main.cpp:(.text._ZNK6mysqlx4abi22r05Value5printERSo[_ZNK6mysqlx4abi22r05Value5printERSo]+0x7d4): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o: in function `_GLOBAL__sub_I_main':
main.cpp:(.text.startup+0x91): undefined reference to `vtable for mysqlx::abi2::r0::DbDoc'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o:(.data.rel.ro._ZTIN6mysqlx4abi22r05ValueE[_ZTIN6mysqlx4abi22r05ValueE]+0x28): undefined reference to `typeinfo for mysqlx::abi2::r0::common::Value'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o:(.data.rel.ro._ZTCN6mysqlx4abi22r05ValueE0_NS1_6common5ValueE[_ZTVN6mysqlx4abi22r05ValueE]+0x18): undefined reference to `typeinfo for mysqlx::abi2::r0::common::Value'
/usr/bin/ld: CMakeFiles/kt-innodb-buffer-page-list.dir/main.cpp.o:(.data.rel.ro._ZTCN6mysqlx4abi22r05ValueE0_NS1_6common5ValueE[_ZTVN6mysqlx4abi22r05ValueE]+0x20): undefined reference to `mysqlx::abi2::r0::common::Value::print(std::ostream&) const'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
make[2]: *** [innodb-buffer-page-list/CMakeFiles/kt-innodb-buffer-page-list.dir/build.make:99: innodb-buffer-page-list/kt-innodb-buffer-page-list] エラー 1
make[1]: *** [CMakeFiles/Makefile2:142: innodb-buffer-page-list/CMakeFiles/kt-innodb-buffer-page-list.dir/all] エラー 2
make: *** [Makefile:91: all] エラー 2

何を言っているのか全くわからない。色々と原因はありそうだが、基本的にリンクをミスってる。
この場合は黙って g++ を使って最小構成の実装をビルドしてみると良い。大体、何が原因かわかる。

参考:
Undefined reference using the mysql c++ connector - Stack Overflow

deb でなく tar.gz から入れた場合に mysqlcppconn8 が見つからないと怒られる

コンパイルする時に怒られる場合がある。LD_LIBRARY_PATH を設定すれば行けた。cmake なら find_library で行ける。

おまけ

cmake でいい感じにする。

流石に Makefile を自分で書くのは面倒だったので cmake に頼りたいが find_package でどうにかなる雰囲気がなかったので多少どうにかする必要があった。そして何故か CMake と MySQL Connector/C++ を併用している人がいないので参考になる実装も見当たらないという中々な状況。
未来の自分のために残しておく。これでさっきのサンプルがビルドできる。

cmake_minimum_required(VERSION 3.8)

project("sample cmake build" VERSION 0.0.1)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_COMPILER "/usr/bin/g++" CACHE STRING "gnu c++ compiler" FORCE)
set(CMAKE_C_COMPILER "/usr/bin/gcc" CACHE STRING "gnu cc compiler" FORCE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O3 -std=c++11")

set(MYSQL_CONNECTOR_CPP_LIBS_DIR "/usr/lib/x86_64-linux-gnu")
set(MYSQL_CONNECTOR_INCLUDE_DIR "/usr/include/mysql-cppconn-8")
find_library(
        MYSQL_CONN_CPP_LIBS
        NAMES mysqlcppconn8 libmysqlconncpp8
        HINTS "${MYSQL_CONNECTOR_CPP_LIBS_DIR}"
        NO_DEFAULT_PATH
)
message("-- mysql connector libs - ${MYSQL_CONN_CPP_LIBS}")

file(GLOB main_src "*.cpp")

add_executable(main ${main_src})

target_include_directories(main PRIVATE ${MYSQL_CONNECTOR_INCLUDE_DIR})
target_link_libraries(main ${MYSQL_CONN_CPP_LIBS})

余談

セッションをどうやら作成できればあとはクエリなどでよろしくできるらしいということは数少ないサンプル実装からわかっているが、それに相当するドキュメントが見当たらないので実質 MySQL Connector/C++ の実装がドキュメントである。
mysqlx::Session から実装を読み解いていくと何となくわかるので、コメントと共に困ったときは読むべきかもしれない。

おわりに

自分の C++ 力、CMake 力、リンクなどの事柄に対する理解不足という甘さが招いたことも多かったという状態に加えて
あまり参考になるドキュメントがないということも相まってだいぶきつかった。これだけで先週の金曜から一週間も溶かしてしまった。

割と罠いところでコケている人が見受けられるので、誰かの助けになることを願って。直近では明日あたりの自分がまた見そう。