C++でMySQL


諸事情により、C++からMySQLを使わなければならなくなりました。
今後のためにも、手順などのメモを残しておきます。


## 動作環境
* OS : CentOS 6.5
* MySQL : 5.5.40
* MySQL Connector/C++ : 1.1.3
* g++ : 4.4.7
* Boost : 1.41
* cmake : 2.6.4

## ドライバ(MySQL Connector/C++)のダウンロード

MySQL Serverへ接続するためのドライバは、下記よりダウンロードできます。
※ ドライバをダウンロードするにはOracleのアカウントが必要なので、事前に取得しておく必要があります。

http://dev.mysql.com/downloads/connector/cpp/


今回はソースからビルドするので、Select Platformは"Source Code"を選択します。

そしてmysql-connector-c++-X.X.X.tar.gzをダウンロードします。
2014.12.26時点での最新バージョンは1.1.5ですが、私が使っていたのは1.1.3です。
以降のメモは1.1.3のものなので、1.1.5とは異なる部分が出てくると思いますが、ご了承下さい。


## ドライバのビルド及びインストール

tar.gzファイルをダウンロードできたら、任意のディレクトリにコピーし、展開します。
展開が完了したらビルドを行うのですが、以下のパッケージがインストールされている必要があります。

* mysql-devel
* boost
* boost-devel
* cmake

INSTALL(展開後にできるテキストファイル)によると、boostは1.34.0以上、cmakeは2.6.2以上が必要ですが、
いずれもyum経由でインストールできます。


これからビルドを行いますが、インストールディレクトリを/usr/local/libから/usr/local/lib64に変更したいので、driver/CMakeLists.txtの以下を編集します(1.1.5では少し異なります)。

<br />

```driver/CMakeLists.txt(275～281行目)
RUNTIME DESTINATION lib ⇒ RUNTIME DESTINATION lib64 
ARCHIVE DESTINATION lib ⇒ ARCHIVE DESTINATION lib64 
LIBRARY DESTINATION lib ⇒ LIBRARY DESTINATION lib64
ARCHIVE DESTINATION lib ⇒ ARCHIVE DESTINATION lib64
```

<br />

編集が完了したらビルドし、インストールします。
`
  cd mysql-connector-c++-1.1.3
  sudo cmake . -DBOOST_ROOT:STRING=/usr/lib64
  sudo make
  sudo make install
`


問題なければ、/usr/local/includeにヘッダファイル、/usr/local/lib64にライブラリがインストールされます。
※ /usr/local直下に不要なテキストファイルがコピーされた場合、それらを削除します。


## ライブラリパスの更新

プログラムからリンクする際、いちいちディレクトリを指定しなくてもいいようにライブラリパスの情報を更新します。

/etc/ld.so.conf.d以下にmysql-connector-cpp-x86_64.confを作成

```lang:mysql-connector-cpp-x86_64.conf
/usr/local/lib64
```

ファイルを作ったら、`ldconfig`コマンドを実行します。


## MySQL Connector/C++のラッパークラス

以下のソースを参考にして作成しました。
https://github.com/eduardocasas/MySQL-Connector-Cpp-Wrapper-Class

<br />

```cpp:MySQLConnWrapper.h
#ifndef _MYSQLCONNWRAPPER_H_
#define _MYSQLCONNWRAPPER_H_

#include <vector>
#include <stdexcept>

#include <mysql_connection.h>
#include <mysql_driver.h>

#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>



class MySQLConnWrapper {

	public:
		MySQLConnWrapper(
			const std::string host, const std::string dbName,
			const std::string user, const std::string passwd);
		virtual ~MySQLConnWrapper();


		void manageException(sql::SQLException& e);
		void connectDb();
		void switchDb();
		void closeDb();

		void executeQuery(const std::string& sql);

		void setPrepStmt(const std::string& query);
		void executePrepStmt();

		void setInt(const int& num, const int& data);
		void setDouble(const int& num, const double& data);
		void setString(const int& num, const std::string& data);
		void setDateTime(const int& num, const std::string& data);
		void setNull(const int& num);

		void executeFetch(const std::string key, std::string& result);
		void executeFetch(const std::string key, std::vector<std::string>& results);
		void executeFetch(const std::vector<std::string>& keys, std::vector<std::string>& results);
		void executeFetch(const std::vector<std::string>& keys, std::vector< std::vector<std::string> >& results);


	private:

		std::string             _host;
		std::string             _dbName;
		std::string             _port;
		std::string             _user;
		std::string             _passwd;

		sql::Driver*            _driver;
		sql::Connection*        _con;
		sql::Statement*         _stmt;
		sql::ResultSet*         _res;
		sql::PreparedStatement* _prepStmt;

};


#endif /*_MYSQLCONNWRAPPER_H_ */
```

<br />

```cpp:MySQLConnWrapper.cpp
#include "MySQLConnWrapper.h"


MySQLConnWrapper::MySQLConnWrapper(
	const std::string host, const std::string dbName,
	const std::string user, const std::string passwd){

	_host   = host;
	_dbName = dbName;
	_user   = user;
	_passwd = passwd;

}


MySQLConnWrapper::~MySQLConnWrapper(){

	if(!_res)      delete _res;
	if(!_stmt)     delete _stmt;
	if(!_prepStmt) delete _prepStmt;
	if(!_con)      delete _con;

}


/**
 * Create an error message related to SQL Exception
 */
void MySQLConnWrapper::manageException(sql::SQLException& e){

	if(e.getErrorCode() != 0){

		std::string msg = "SQL Exception : " + static_cast<std::string>(e.what());
		msg += " (MySQL error code : " + e.getErrorCode();
		msg += ", SQLState : " + e.getSQLState() + " )\n";

		std::cerr << msg << std::endl;

	}

}


/**
 * Connect to mysql server
 */
void MySQLConnWrapper::connectDb(){

	try{

		_driver = sql::mysql::get_driver_instance();
		_con    = _driver->connect(_host, _user, _passwd);

	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Select a database
 */
void MySQLConnWrapper::switchDb(){

	if(!_con){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "System is not still connected to the database.";
		throw std::runtime_error(msg);
	}

	try{

		_con->setSchema(_dbName);
		_stmt = _con->createStatement();

	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Disconnect to mysql server
 */
void MySQLConnWrapper::closeDb(){

	if(!_con){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "System is not still connected to the database.";
		throw std::runtime_error(msg);
	}

	_con->close();

}


/**
 * Execute a query directly
 */
void MySQLConnWrapper::executeQuery(const std::string& sql){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Statement is not set.";
		throw std::runtime_error(msg);
	}

	try{
		_res = _stmt->executeQuery(sql);
	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Set a prepared statement
 */
void MySQLConnWrapper::setPrepStmt(const std::string& query){

	if(!_con){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "System is not still connected to the database.";
		throw std::runtime_error(msg);
	}

	try{
		_prepStmt = _con->prepareStatement(query);
	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Excecute a prepared statement
 */
void MySQLConnWrapper::executePrepStmt(){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Prepared Statement is not set.";
		throw std::runtime_error(msg);
	}

	try{
		_res = _prepStmt->executeQuery();
	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Fetch the result(using single key) and assign to a string variable
 */
void MySQLConnWrapper::executeFetch(const std::string key, std::string& result){

	try{

		if(_res->next()){
			result = _res->getString(key);
		}

	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Fetch the results(using single key) and assign to a string vector
 */
void MySQLConnWrapper::executeFetch(const std::string key, std::vector<std::string>& results){

	try{

		while(_res->next()){
			results.push_back(_res->getString(key));
		}

	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Fetch the results(using sevral keys) and assign to a string vector
 */
void MySQLConnWrapper::executeFetch(const std::vector<std::string>& keys,
	std::vector<std::string>& results){

	std::vector<std::string>::const_iterator it;

	try{
		if(_res->next()){

			for(it = keys.begin(); it != keys.end(); it++){
				results.push_back(_res->getString(*it));
			}
		}
	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}


/**
 * Fetch the results(using sevral keys) and assign to string vectors
 */
void MySQLConnWrapper::executeFetch(const std::vector<std::string>& keys,
		std::vector< std::vector<std::string> >& results){

	std::vector<std::string>::const_iterator it;

	try{
		while(_res->next()){

			std::vector<std::string> values;
			for(it = keys.begin(); it != keys.end(); it++){
				values.push_back(_res->getString(*it));
			}

			results.push_back(values);

		}

	}catch(sql::SQLException& e){

		this->manageException(e);
		throw;

	}

}



/**
 * Set a int value to a prepared statement
 */
void MySQLConnWrapper::setInt(const int& num, const int& data){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Prepared Statement is not set.";
		throw std::runtime_error(msg);
	}

	_prepStmt->setInt(num, data);

}


/**
 * Set a double value to a prepared statement
 */
void MySQLConnWrapper::setDouble(const int& num, const double& data){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Prepared Statement is not set.";
		throw std::runtime_error(msg);
	}

	_prepStmt->setDouble(num, data);

}


/**
 * Set a string value to a prepared statement
 */
void MySQLConnWrapper::setString(const int& num, const std::string& data){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Prepared Statement is not set.";
		throw std::runtime_error(msg);
	}

	_prepStmt->setString(num, data);

}


/**
 * Set a date value (data type is string) to a prepared statement
 */
void MySQLConnWrapper::setDateTime(const int& num, const std::string& data){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Prepared Statement is not set.";
		throw std::runtime_error(msg);
	}

	_prepStmt->setDateTime(num, data);

}


/**
 * Set a NULL value to a prepared statement
 */
void MySQLConnWrapper::setNull(const int& num){

	if(!_stmt){
		std::string msg = std::string(__func__) + " Failed. ";
		msg += "Prepared Statement is not set.";
		throw std::runtime_error(msg);
	}

	// The second argument of setNull is a int variable.
	// However, since this variable is not used in mysqlcppconn, set a NULL.
	_prepStmt->setNull(num, NULL);

}
```

<br />
SQLの実行は`executeQuery`か`executePrepStmt`を使う形になるため、ほぼSQL文を直接書きます。
※ 使用用途が限られていたため、これで十分でした。

結果の取得は`executeFetch`を使って、すべて`string`型で受け取るようにしています。
なので、値の型変換はこのクラスを使う側にて行う形になります。

`executeFetch`としてオーバーロードされる関数は4つあり、それぞれ以下のように区別しています。

<br />

* **`executeFetch(const std::string key, std::string& result)`**

|key|
|:-:|
|value|

SELECT文の結果が、**1つのkeyに対して1つのvalue**となる場合に使用
<br />

* **`executeFetch(const std::string key, std::vector<std::string>& results)`**

|key|
|:-:|
|value1|
|value2|
|value3|

SELECT文の結果が、**1つのkeyに対して複数のvalue**となる場合に使用
<br />


* **`executeFetch(const std::vector<std::string>& keys, std::vector<std::string>& results)`**

|key1|key2|key3|
|:-:|:-:|:-:|
|value1|value2|value3|

SELECT文の結果が、**複数のkeyに対してそれぞれ1つのvalue**となる場合に使用
<br />


* **`executeFetch(const std::vector<std::string>& keys, std::vector< std::vector<std::string> >& results)`**

|key1|key2|key3|
|:-:|:-:|:-:|
|value11|value12|value13|
|value21|value22|value23|
|value31|value32|value33|

SELECT文の結果が、**複数のkeyに対してそれぞれ複数のvalue**となる場合に使用
<br />
<br />

公開するのが恥ずかしいぐらいの実装かもしれないのですが、限られた用途だと意外に使いやすかったです。